2012-02-12 2 views
2

Я не понимаю, почему я должен получать содержимое 2D-массива в b[][3], а не в **b? Также как мы можем сделать вызов по значению для 2D-массивов? Кроме того, адрес 2D-массива arr равен arr равен *arr равен &arr[0][0]; все адреса одинаковы. Я не могу ясно представить это; может ли кто-нибудь объяснить мне, как многомерный массив фактически хранится. «Приветствуются живописные полезные ссылки».Разница между ** переменной и переменной [] []?

#include "hfile.h" // contains all needed H files 

void caller(int b[][3]) // why can't we write **b? 
{ 
    int k=100; 
    printf("\n****Caller:****\n"); 

    for(int i=0;i<3;i++) 
    { 
     for(int j=0;j<3;j++) 
     { 
      b[i][j]=k++; 
      printf("\t %d",b[i][j]); 
     } 
     printf("\n"); 
    } 
} 

int main() 
{ 
    int arr[3][3]={1,2,3,4,5,6,7,8,9}; // original containts 

    caller(arr);    // Called caller function passing containts of "arr" 

    printf("\n****Orignal****\n"); 
    for(int i=0;i<3;i++) 
    { 
     for(int j=0;j<3;j++) 
      printf("\t %d",arr[i][j]);   

     printf("\n"); 
    } 
    return 0; 
} 
+0

Что происходит, когда вы используете 'int ** b'? Некоторая ошибка компилятора? – user1025189

+0

Да Ошибка: невозможно преобразовать int [3] * в int ** – user1171901

+0

Прочтите раздел 6 [comp.lang.c FAQ] (http://c-faq.com). –

ответ

1

Правила ASCII Art!

Давайте посмотрим на 2D-массив наглядно. Предположим, что массив имеет 2 байта short целых чисел и что адреса также являются 2 байтами. Это может быть чип Zilog Z80, если хотите, но это только для удобства хранения чисел.

short A[3][3]; 

+---------+---------+---------+ 
| A[0][0] | A[0][1] | A[0][2] | 
+---------+---------+---------+ 
| A[1][0] | A[1][1] | A[1][2] | 
+---------+---------+---------+ 
| A[2][0] | A[2][1] | A[2][2] | 
+---------+---------+---------+ 

Давайте предположим, адрес: A = 0x4000short * адреса элементов массива, то есть:

&A[0][0] = 0x4000; 
&A[0][1] = 0x4002; 
&A[0][2] = 0x4004; 
&A[1][0] = 0x4006; 
&A[1][1] = 0x4008; 
&A[1][2] = 0x400A; 
&A[2][0] = 0x400C; 
&A[2][1] = 0x400E; 
&A[2][2] = 0x4010; 

Теперь, следует также заметить, что вы можете написать:

&A[0] = 0x4000; 
&A[1] = 0x4006; 
&A[2] = 0x400C; 

Типы этих указателей «указатель на массив [3] из short ', или short (*A)[3].

Вы также можете написать:

&A  = 0x4000; 

тип это 'указатель на массив [3] [3] из short' или short (*A)[3][3].

Одним из ключевых различий в размерах объекта, как показывает этот код:

#include <stdio.h> 
#include <inttypes.h> 

static void print_address(const char *tag, uintptr_t address, size_t size); 

int main(void) 
{ 
    char buffer[32]; 
    short A[3][3] = { { 0, 1, 2 }, { 3, 4, 5 }, { 6, 7, 8 } }; 
    int i, j; 

    print_address("A", (uintptr_t)A, sizeof(A)); 
    print_address("&A", (uintptr_t)&A, sizeof(*(&A))); 

    for (i = 0; i < 3; i++) 
    { 
     for (j = 0; j < 3; j++) 
     { 
      sprintf(buffer, "&A[%d][%d]", i, j); 
      print_address(buffer, (uintptr_t)&A[i][j], sizeof(*(&A[i][j]))); 
     } 
    } 

    for (i = 0; i < 3; i++) 
    { 
     sprintf(buffer, "&A[%d]", i); 
     print_address(buffer, (uintptr_t)&A[i], sizeof(*(&A[i]))); 
    } 

    putchar('\n'); 
    for (i = 0; i < 3; i++) 
    { 
     for (j = 0; j < 3; j++) 
     { 
      printf(" A[%d][%d] = %d", i, j, A[i][j]); 
     } 
     putchar('\n'); 
    } 

    return 0; 
} 

static void print_address(const char *tag, uintptr_t address, size_t size) 
{ 
    printf("%-8s = 0x%.4" PRIXPTR " (size %zu)\n", tag, address & 0xFFFF, size); 
} 

Эта программа фальсифицирует 16-битовые адреса с операцией маскирования в функции print_address().

Вывод при компиляции в 64-разрядном режиме на MacOS X 10.7.2 (GCC 'i686-apple-darwin11-llvm-gcc-4.2 (GCC) 4.2.1 (на основе Apple Inc. build 5658) (LLVM построить 2335.15.00) '), был:

A   = 0xD5C0 (size 18) 
&A  = 0xD5C0 (size 18) 
&A[0][0] = 0xD5C0 (size 2) 
&A[0][1] = 0xD5C2 (size 2) 
&A[0][2] = 0xD5C4 (size 2) 
&A[1][0] = 0xD5C6 (size 2) 
&A[1][1] = 0xD5C8 (size 2) 
&A[1][2] = 0xD5CA (size 2) 
&A[2][0] = 0xD5CC (size 2) 
&A[2][1] = 0xD5CE (size 2) 
&A[2][2] = 0xD5D0 (size 2) 
&A[0]  = 0xD5C0 (size 6) 
&A[1]  = 0xD5C6 (size 6) 
&A[2]  = 0xD5CC (size 6) 

    A[0][0] = 0 A[0][1] = 1 A[0][2] = 2 
    A[1][0] = 3 A[1][1] = 4 A[1][2] = 5 
    A[2][0] = 6 A[2][1] = 7 A[2][2] = 8 

Я составил вариант без операции маскирования в 32-битном режиме, и получил выход:

A   = 0xC00E06D0 (size 18) 
&A  = 0xC00E06D0 (size 18) 
&A[0][0] = 0xC00E06D0 (size 2) 
&A[0][1] = 0xC00E06D2 (size 2) 
&A[0][2] = 0xC00E06D4 (size 2) 
&A[1][0] = 0xC00E06D6 (size 2) 
&A[1][1] = 0xC00E06D8 (size 2) 
&A[1][2] = 0xC00E06DA (size 2) 
&A[2][0] = 0xC00E06DC (size 2) 
&A[2][1] = 0xC00E06DE (size 2) 
&A[2][2] = 0xC00E06E0 (size 2) 
&A[0]  = 0xC00E06D0 (size 6) 
&A[1]  = 0xC00E06D6 (size 6) 
&A[2]  = 0xC00E06DC (size 6) 

    A[0][0] = 0 A[0][1] = 1 A[0][2] = 2 
    A[1][0] = 3 A[1][1] = 4 A[1][2] = 5 
    A[2][0] = 6 A[2][1] = 7 A[2][2] = 8 

а в 64-битном режиме, выход из варианта был:

A   = 0x7FFF65BB15C0 (size 18) 
&A  = 0x7FFF65BB15C0 (size 18) 
&A[0][0] = 0x7FFF65BB15C0 (size 2) 
&A[0][1] = 0x7FFF65BB15C2 (size 2) 
&A[0][2] = 0x7FFF65BB15C4 (size 2) 
&A[1][0] = 0x7FFF65BB15C6 (size 2) 
&A[1][1] = 0x7FFF65BB15C8 (size 2) 
&A[1][2] = 0x7FFF65BB15CA (size 2) 
&A[2][0] = 0x7FFF65BB15CC (size 2) 
&A[2][1] = 0x7FFF65BB15CE (size 2) 
&A[2][2] = 0x7FFF65BB15D0 (size 2) 
&A[0]  = 0x7FFF65BB15C0 (size 6) 
&A[1]  = 0x7FFF65BB15C6 (size 6) 
&A[2]  = 0x7FFF65BB15CC (size 6) 

    A[0][0] = 0 A[0][1] = 1 A[0][2] = 2 
    A[1][0] = 3 A[1][1] = 4 A[1][2] = 5 
    A[2][0] = 6 A[2][1] = 7 A[2][2] = 8 

В 32-битных и 64-разрядных версиях адресов много шума, поэтому мы можем сохранить версию псевдо-16-разрядного адреса.

Обратите внимание, что адрес A[0][0] совпадает с адресом A[0] и A, но размеры объекта, на который указывает, отличаются. &A[0][0] указывает на одно (короткое) целое число; &A[0] указывает на массив из 3 (коротких) целых чисел; &A указывает на массив из 3x3 (коротких) целых чисел.

Теперь нам нужно посмотреть, как работает short **; он работает совсем по-другому. Вот несколько тестовых кодов, связанных, но отличающихся от предыдущего.

#include <stdio.h> 
#include <inttypes.h> 

static void print_address(const char *tag, uintptr_t address, size_t size); 

int main(void) 
{ 
    char buffer[32]; 
    short t[3] = { 99, 98, 97 }; 
    short u[3] = { 88, 87, 86 }; 
    short v[3] = { 77, 76, 75 }; 
    short w[3] = { 66, 65, 64 }; 
    short x[3] = { 55, 54, 53 }; 
    short y[3] = { 44, 43, 42 }; 
    short z[3] = { 33, 32, 31 }; 
    short *a[3] = { t, v, y }; 
    short **p = a; 
    int i, j; 

    print_address("t", (uintptr_t)t, sizeof(t)); 
    print_address("u", (uintptr_t)u, sizeof(u)); 
    print_address("v", (uintptr_t)v, sizeof(v)); 
    print_address("w", (uintptr_t)w, sizeof(w)); 
    print_address("x", (uintptr_t)x, sizeof(x)); 
    print_address("y", (uintptr_t)y, sizeof(y)); 
    print_address("z", (uintptr_t)z, sizeof(z)); 

    print_address("a", (uintptr_t)a, sizeof(a)); 
    print_address("&a", (uintptr_t)&a, sizeof(*(&a))); 

    for (i = 0; i < 3; i++) 
    { 
     for (j = 0; j < 3; j++) 
     { 
      sprintf(buffer, "&a[%d][%d]", i, j); 
      print_address(buffer, (uintptr_t)&a[i][j], sizeof(*(&a[i][j]))); 
     } 
    } 

    for (i = 0; i < 3; i++) 
    { 
     sprintf(buffer, "&a[%d]", i); 
     print_address(buffer, (uintptr_t)&a[i], sizeof(*(&a[i]))); 
    } 

    putchar('\n'); 
    for (i = 0; i < 3; i++) 
    { 
     for (j = 0; j < 3; j++) 
     { 
      printf(" a[%d][%d] = %d", i, j, a[i][j]); 
     } 
     putchar('\n'); 
    } 

    putchar('\n'); 
    print_address("p", (uintptr_t)p, sizeof(*(p))); 
    print_address("&p", (uintptr_t)&p, sizeof(*(&p))); 

    for (i = 0; i < 3; i++) 
    { 
     for (j = 0; j < 3; j++) 
     { 
      sprintf(buffer, "&p[%d][%d]", i, j); 
      print_address(buffer, (uintptr_t)&p[i][j], sizeof(*(&p[i][j]))); 
     } 
    } 

    for (i = 0; i < 3; i++) 
    { 
     sprintf(buffer, "&p[%d]", i); 
     print_address(buffer, (uintptr_t)&p[i], sizeof(*(&p[i]))); 
    } 

    putchar('\n'); 
    for (i = 0; i < 3; i++) 
    { 
     for (j = 0; j < 3; j++) 
     { 
      printf(" p[%d][%d] = %d", i, j, p[i][j]); 
     } 
     putchar('\n'); 
    } 

    return 0; 
} 

static void print_address(const char *tag, uintptr_t address, size_t size) 
{ 
    printf("%-8s = 0x%.4" PRIXPTR " (size %zu)\n", tag, address & 0xFFFF, size); 
} 

Это программа в две половины. Одна половина анализирует массив a; другой рассекает двойной указатель p. Вот некоторые ASCII искусство, чтобы помочь понять это:

+------+------+------+      +------+------+------+ 
| 99 | 98 | 97 | t = 0x1000  | 88 | 87 | 86 | u = 0x1100 
+------+------+------+      +------+------+------+ 

+------+------+------+      +------+------+------+ 
| 77 | 76 | 75 | v = 0x1200  | 66 | 65 | 64 | w = 0x1300 
+------+------+------+      +------+------+------+ 

+------+------+------+      +------+------+------+ 
| 55 | 54 | 53 | x = 0x1400  | 44 | 43 | 42 | y = 0x1500 
+------+------+------+      +------+------+------+ 

+------+------+------+ 
| 33 | 32 | 31 | z = 0x1600 
+------+------+------+ 

+--------+--------+--------+ 
| 0x1000 | 0x1200 | 0x1500 | a = 0x2000 
+--------+--------+--------+ 

+--------+ 
| 0x2000 |      p = 0x3000 
+--------+ 

Обратите внимание, что массивы t .. z расположены в «произвольных» местах - не соприкасающихся на диаграмме. Некоторые из массивов могут быть глобальными переменными, например, из другого файла, а другие - статическими переменными в одном файле, но вне функции, а другие - статическими, но локальными для функции, а также эти локальные автоматические переменные. Вы можете видеть, как p - это переменная, содержащая адрес; адрес - адрес массива a.В свою очередь массив a содержит 3 адреса, адреса 3 других массивов.

Это результат 64-разрядной компиляции программы, искусственно разделенной. Он имитирует 16-разрядные адреса, маскируя все, за исключением последних четырех цифр шестнадцатеричного адреса.

t   = 0x75DA (size 6) 
u   = 0x75D4 (size 6) 
v   = 0x75CE (size 6) 
w   = 0x75C8 (size 6) 
x   = 0x75C2 (size 6) 
y   = 0x75BC (size 6) 
z   = 0x75B6 (size 6) 

Это предотвращает предупреждения о неиспользуемых переменных, а также идентифицирует адреса 7 массивов из 3 целых чисел.

a   = 0x7598 (size 24) 
&a  = 0x7598 (size 24) 
&a[0][0] = 0x75DA (size 2) 
&a[0][1] = 0x75DC (size 2) 
&a[0][2] = 0x75DE (size 2) 
&a[1][0] = 0x75CE (size 2) 
&a[1][1] = 0x75D0 (size 2) 
&a[1][2] = 0x75D2 (size 2) 
&a[2][0] = 0x75BC (size 2) 
&a[2][1] = 0x75BE (size 2) 
&a[2][2] = 0x75C0 (size 2) 
&a[0]  = 0x7598 (size 8) 
&a[1]  = 0x75A0 (size 8) 
&a[2]  = 0x75A8 (size 8) 

    a[0][0] = 99 a[0][1] = 98 a[0][2] = 97 
    a[1][0] = 77 a[1][1] = 76 a[1][2] = 75 
    a[2][0] = 44 a[2][1] = 43 a[2][2] = 42 

Обратите внимание на важные отличия. Размер a теперь составляет 24 байта, а не 18, потому что это массив из 3 (64-разрядных) указателей. Размер &a[n] составляет 8 байтов, поскольку каждый из них является указателем. Метод загрузки данных в местоположении массива также совсем другой - вам нужно посмотреть на ассемблер, чтобы увидеть это, потому что исходный код C выглядит одинаково.

В коде 2D массива, операция загрузки для A[i][j] вычисляет:

  • байт адрес A
  • добавляет (3 * i + j) * sizeof(short) в этот байт адреса
  • извлекает 2-байтовое целое число от этого адреса.

В массиве кода указателя, операции загрузки для A[i][j] вычисляет:

  • байт адрес a
  • добавляет i * sizeof(short *) в этот байт адреса
  • извлекает байт адреса из этого вычисленного значения, его называют b
  • добавляет j * sizeof(short) в b
  • извлекает 2-байтовое целое число от адреса b

Выход для p несколько отличается. Обратите внимание, в частности, адрес вp отличается от отp. Однако, как только вы закончите это, поведение в основном то же самое.

p   = 0x7598 (size 8) 
&p  = 0x7590 (size 8) 
&p[0][0] = 0x75DA (size 2) 
&p[0][1] = 0x75DC (size 2) 
&p[0][2] = 0x75DE (size 2) 
&p[1][0] = 0x75CE (size 2) 
&p[1][1] = 0x75D0 (size 2) 
&p[1][2] = 0x75D2 (size 2) 
&p[2][0] = 0x75BC (size 2) 
&p[2][1] = 0x75BE (size 2) 
&p[2][2] = 0x75C0 (size 2) 
&p[0]  = 0x7598 (size 8) 
&p[1]  = 0x75A0 (size 8) 
&p[2]  = 0x75A8 (size 8) 

    p[0][0] = 99 p[0][1] = 98 p[0][2] = 97 
    p[1][0] = 77 p[1][1] = 76 p[1][2] = 75 
    p[2][0] = 44 p[2][1] = 43 p[2][2] = 42 

Все это было в виде одной (основной) функции. Вам нужно будет провести параллельное экспериментирование самостоятельно, передавая различные указатели на функции и получая доступ к массивам за этими указателями.

+0

Спасибо .. (хотя мне потребуется некоторое время, чтобы полностью понять вашу разработку), теперь это мотивировало меня .. :) – user1171901

5

Если объявить многомерный массив:

int b[M][N]; 

хранение смежный. Поэтому, когда вы получаете доступ к элементу, например. (x = b[i][j];), компилятор выдает код, эквивалентный это:

int *c = (int *)b; // Treat as a 1D array 
int k = (i*N + j); // Offset into 1D array 
x = c[k]; 

Когда доступ к элементу через указатель указатель на компилятор не имеет знания о размерах, и он производит такой код:

int *t = b[i]; // Follow first pointer (produces another pointer) 
x = t[j];  // Follow second pointer 

т. Е. Он просто следует указателям.

Это совершенно несовместимо, поэтому компилятор препятствует передаче истинного 2D-массива в функцию с указателем на указатель.

0
void caller(int b[][3]) // why can't we write **b ? 

Вы можете написать int **b, но тогда вы не можете передать arr этой функции, так как arr определяется как int arr[3][3], несовместимой с int ** типа.

arr может преобразовывать в int (*)[3], но не в int **. Таким образом, вы можете написать это:

void caller(int (*b)[3]) //ok 

На самом деле int[3][3] определяет массив массив , в то время как int** определяет указатель на указатель. int[3][3] может преобразовывать в указатель к массиву из 3 int (что равно int (*)[3]), так же как int[3] может преобразовывать в указатель int (что равно int*).

1. Точнее, он определяет массив из 3 массивов-3-int.

Смежные вопросы