Преобразование адреса массива в другие типы данных

int main()
{
    char arr[5][7][6];
    char (*p)[5][7][6] = &arr;
    printf("%d\n", (&arr + 1) - &arr);
    printf("%d\n", (char *)(&arr + 1) - (char *)&arr);
    printf("%d\n", (unsigned)(arr + 1) - (unsigned)arr);
    printf("%d\n", (unsigned)(p + 1) - (unsigned)p);

    return 0;
}

Когда я запускаю вышеуказанный код, я получаю следующий вывод:

 1
 210 
 42
 210

Почему вывод не 1 в каждом случае?

Ответ 1

Примечание &arr - это полный 3-мерный адрес массива char, тогда как arr указывает на первый элемент, который является двумерным массивом char. Что-то вроде ниже в диаграмме:

 0xbf8ce2c6
+------------------+     ◄-- arr  =  0xbf8ce2c6  
|    0xbf8ce2f0    |  
|   +------------------+     ◄-- arr + 1 = 0xbf8ce2f0
|   |   0xbf8ce31a |   |
|   |   +------------------+      ◄-- arr + 2 = 0xbf8ce31a 
|   |   0xbf8ce344 |   |   |
|   |   |   +------------------+      ◄-- arr + 3 = 0xbf8ce344
|   |   0xbf8ce36e |   |   |   |
|   |   |   |  +------------------+      ◄-- arr + 4 = 0xbf8ce36e
|   |   |   |  |   |   |   |   |  |
+---|---|---|--|---+   |   |   |  |  Each are 7*6, 2-Dimensional 
    |   |   |  |       |   |   |  |  Consists Of 42 bytes 
    +---|---|--|-------+   |   |  |  
        |   |  |           |   |  |
        +---|--|-----------+   |  |
            |  |               |  |
            +--|---------------+  |
               |                  |
               +------------------+

 The diagram show: 
 1. How a 3-dimensional can be interpreted as series of 2-dimensional arrays
 2. Here (arr + i) points to a 2-D array 
 3. Notice difference between: (arr + i + 1) - (arr + i) = 0x2a = 42, where i = [0, 4]

Тип &arr - char(*)[5][7][6], который является адресом char 3D-массива измерения [5][7][6]. Значительная разница между &arr и &arr + 1 равна 5 * 7 * 6 * sizeof(char)= 210.
Поскольку размер char[5][7][6] равен 5 * 7 * 6 * sizeof(char).
В вашем коде &arr указывается на трехмерный массив и &arry + 1 следующий трехмерный массив (который не существует в нашем коде).

Проверьте этот рабочий код на codepade:

int main()
{
    char arr[5][7][6];
    printf(" &arr  : %p", &arr);
    printf(" &arr+1: %p", &arr + 1);

    return 0;
}

Выход:

 &arr  : 0xbf5dd7de
 &arr+1: 0xbf5dd8b0

Разница между (&arr + 1) - (&arr)= 0xbf5dd8b0 - 0xbf5dd7de= 0xd2= 210.

Во втором printf:

printf("%d\n", (char *)(&arr + 1) - (char *)&arr);

Вы присваиваете адреса типам char(*)[5][7][6] равным (char*), а поскольку sizeof char[5][7][6] 210, оба адреса 210 далеки. (помните sizeof(char) == 1). Это причина выхода: 210

Теперь, как я сказал в первом утверждении, arr - это адрес первого элемента, который представляет собой двумерный массив символов. Тип arr - char(*)[7][6]. Теперь один элемент (двумерный массив размера 6 * 7 * sizeof(char) = 42).
(Примечание: вы можете представить трехмерный массив в виде массива один-d, где каждый элемент представляет собой 2-мерный массив).

В третьей версии printf:

printf("%d\n", (unsigned)(arr + 1) - (unsigned)arr);

Вы присваиваете значения методу unsigned (но не к типу адреса/указателя). Разница между arr + 1 и arr равна 42 * sizeof(char)= 42 (то есть равна размеру char[7][6]). Поэтому вывод printf: 42.

Примечание. Вы должны читать sizeof (int) == sizeof (void *)?, потому что вы указываете адрес для значения. и это преобразование не полностью определено. (мое объяснение касается вашего вывода и результата, который я дал).

Для дальнейшего уточнения проверьте ниже рабочий код codepade:

int main()
{
    char arr[5][7][6];
    printf(" arr  : %p\n", arr);
    printf(" arr+1: %p", arr + 1);

    return 0;
}

Выход:

 arr  : 0xbf48367e
 arr+1: 0xbf4836a8

Сделайте разницу между (arr + 1) - (arr)= 0xbf4836a8 - 0xbf48367e= 0x2a= 42.

Последний printf:

 printf("%d\n", (unsigned)(p + 1) - (unsigned)p);

Просто используйте разницу между &arr+1 и &arr= 210 (аналогично второй printf), потому что p является указателем на 3-D char array (= &arr). И вы приписываете его типу значения (не указательному типу).

Кроме того, (просто добавляя для понимания цели, я думаю, читатель найдет это полезным),

Давайте узнаем еще одно различие между arr и &arr, используя оператор sizeof, который поможет вам глубже понять концепцию. Для этого сначала читайте: sizeof Оператор

Когда вы применяете оператор sizeof к идентификатору массива, результатом является размер всего массива, а не размер указателя, представленный идентификатором массива.

Проверьте этот рабочий код на codepade:

int main()
{
    char arr[5][7][6];
    printf(" Sizeof(&arr)  : %lu and value &arr: %p\n", sizeof(&arr), &arr);
    printf(" Sizeof(arr)   : %lu and value arr : %p\n", sizeof(arr), arr);
    printf(" Sizeof(arr[0]): %lu and value a[0]: %p\n",sizeof(arr[0]), arr[0]);
    return 0;
}

Его вывод:

Sizeof(&arr)  : 4 and value &arr: 0xbf4d9eda
Sizeof(arr)   : 210 and value arr : 0xbf4d9eda
Sizeof(arr[0]): 42 and value a[0]: 0xbf4d9eda
  • Здесь &arr - это просто адрес, а в системном адресе - четыре байта, и это адрес полного трехмерного массива char.

  • arr - это имя 3-мерного массива, а оператор sizeof дает общий размер массива 210 = 5 * 7 * 6 * sizeof(char).

Как я показал на моей диаграмме arr, указывает на первые элементы, являющиеся двумерным массивом. Поэтому, поскольку arr= (arr + 0). Теперь с помощью * Оператор разыменования в (arr + 0) дает значение по адресу так *(arr + 0) = arr[0].

  • Примечание sizeof(arr[0]) дает 42= 7 * 6 * sizeof(char). И это доказательство концептуально представляет собой трехмерный массив, но массив двумерного массива.

Потому что выше в моем ответе много раз я писал: "размер char[5][7][6] - 5 * 7 * 6 * sizeof(char)". поэтому я добавляю интересный код ниже @codepade:

int main(){
 printf(" Char         : %lu \n", sizeof(char));
 printf(" Char[5]      : %lu \n", sizeof(char[6]));
 printf(" Char[5][7]   : %lu \n", sizeof(char[7][6]));
 printf(" Char[5][7][6]: %lu \n", sizeof(char[5][7][6]));

 return 1;
}

Вывод:

 Char         : 1 
 Char[5]      : 6 
 Char[5][7]   : 42 
 Char[5][7][6]: 210 

Ответ 2

Хорошо, если бы я хотел разделить волосы: во-первых, код вызывает поведение undefined повсюду, во всех операторах printf(). Разница двух указателей имеет тип ptrdiff_t, и для этого правильный спецификатор преобразования %td, а не %d.

Остальное - только спекуляция. Предположим, что ваша система разумна, а численно значение указателя &arr всегда одно и то же, независимо от того, для какого типа он будет преобразован.

Теперь (&arr + 1) - &arr равно 1, конечно, согласно правилам арифметики указателя. (Фактическая разница между двумя указателями составляет 210 * sizeof(int) байт, но это не школьная математика, а указательная арифметика, поэтому результат дается в единицах размера sizeof(T), где T является базовым типом указателя. )

Затем (char *)(&arr + 1) - (char *)&arr направляет указатели на char *, а так как размер char равен 1, это будет печатать разницу в байтах; вы эффективно обманываете/злоупотребляете указателем арифметики здесь.

Кроме того: printf("%d\n", (unsigned)(arr + 1) - (unsigned)arr) вычитает два указателя типа int (*)[7][6]. То, что arr распадается. Конечно, 7 * 6 = 42, поэтому разница в размерах между arr + 1 и arr составляет 42 элемента.

p, однако, не является указателем на первый элемент массива, но является указателем на сам массив. Его тип правильно обозначается как int (*)[5][6][7]. Теперь, если вы напечатаете разницу с помощью этого типа,, но вы не позволяете компилятору делать деление, обманывая его тем, что указатели просто unsigned, тогда вы получите 5 * 6 * 7, который составляет 210.

Ответ 3

В (&arr + 1) - &arr:

&arr - это адрес массива из 5 массивов из 7 массивов 6 char. Добавление одного из них приводит к тому, что массив next из 5 массивов из 7 массивов из 6 char будет, если бы у нас был массив этих объектов, а не один. Вычитая исходный адрес, &arr, выдает разницу между двумя адресами. По стандарту C эта разница выражается как количество элементов между двумя адресами, где тип элемента - это тип объекта, на который указывают. Поскольку этот тип представляет собой массив из 5 массивов из 7 массивов 6 char, расстояние между двумя адресами является одним элементом. Другими словами, расстояние от &arr до (&arr + 1) составляет один массив из 5 массивов из 7 массивов 6 char.

В (char *)(&arr + 1) - (char *)&arr:

&arr + 1 снова является указателем на то, где будет следующий массив из 5 массивов из 7 массивов 6 char. Когда он преобразуется в char *, результатом является указатель на то, что будет первым байтом этого следующего массива. Аналогично, (char *)&arr является указателем на первый байт первого массива. Тогда вычитание двух указателей дает разницу между ними в элементах. Поскольку эти указатели являются указателями на char, разница получается как число char. Таким образом, разница состоит в числе байтов в массиве из 5 массивов из 7 массивов 6 char, который равен 5 • 7 • 6 char или 210 char.

В (unsigned)(arr + 1) - (unsigned)arr:

Так как arr не используется с & (или sizeof или другими исключительными случаями), он автоматически преобразуется из массива из 5 массивов из 7 массивов 6 char в указатель на первый элемент, Таким образом, это указатель на массив из 7 массивов 6 char. Тогда arr + 1 является указателем на следующий массив из 7 массивов 6 char. Когда этот адрес преобразуется в unsigned, результат в реализации C, который вы используете, фактически является адресом памяти объекта. (Это обычное явление, но не гарантируется стандартом C и, разумеется, прерывается, когда адреса имеют 64 бита, но unsigned - 32 бита.) Аналогично, (unsigned)arr - это адрес первого объекта. Когда адреса вычитаются, результатом является расстояние между ними в байтах. Таким образом, разница - это количество байтов в массиве из 7 массивов 6 char, которое составляет 7 • 6 байтов или 42 байта. Обратите внимание на разницу клавиш в этом случае: &arr является указателем на массив из 5 массивов из 7 массивов 6 char, но arr является указателем на массив из 7 массивов 6 char.

В (unsigned)(p + 1) - (unsigned)p:

p является указателем на массив из 5 массивов из 7 массивов 6 char. Тогда p+1 является указателем на то, где будет следующий массив. Преобразование в unsigned действует, как описано выше. Вычитание дает разницу в байтах, поэтому это размер массива из 5 массивов из 7 массивов 6 char, поэтому он снова 210 байтов.

В стороне:

Тип (&arr + 1) - &arr - ptrdiff_t и должен быть напечатан с помощью %td.

Тип (char *)(&arr + 1) - (char *)&arr равен ptrdiff_t и должен быть напечатан с помощью %td.

Тип (unsigned)(arr + 1) - (unsigned)arr равен unsigned int и должен быть напечатан с помощью %u.

Тип (unsigned)(p + 1) - (unsigned)p равен unsigned int и должен быть напечатан с помощью %u.