Подтвердить что ты не робот

Адрес указателя в многомерном массиве C

Я возился с многомерными массивами и указателями. Я смотрел программу, которая печатает содержимое и адреса простого массива. Здесь объявление массива:

int zippo[4][2] = { {2,4},
            {6,8},
            {1,3},
            {5,7}   };

В настоящее время я понимаю, что zippo - это указатель, и он может содержать адрес пары других указателей. По умолчанию zippo содержит адрес указателя zippo[0], а также может содержать адреса указателей zippo[1], zippo[2] и zippo[3].

Теперь сделайте следующее утверждение:

printf("zippo[0] = %p\n", zippo[0]);
printf("  *zippo = %p\n", *zippo);
printf("   zippo = %p\n", zippo);

На моей машине это дает следующий вывод:

zippo[0] = 0x7fff170e2230
  *zippo = 0x7fff170e2230
   zippo = 0x7fff170e2230

Я прекрасно понимаю, почему zippo[0] и *zippo имеют одинаковое значение. Они оба указателя, и оба они сохраняют адрес (по умолчанию) целого числа 2 или zippo[0][0]. Но что с zippo также разделяет один и тот же адрес памяти? Не следует ли zippo хранить адрес указателя zippo[0]? Whaaaat?

4b9b3361

Ответ 1

Когда вы объявляете многомерный массив, компилятор рассматривает его как одномерный массив. Многомерные массивы - это просто абстракция, облегчающая нашу жизнь. У вас есть недоразумение: это не один массив, указывающий на 4 массива, его всегда просто один сплошной блок памяти.

В вашем случае выполните:

int zippo[4][2]

На самом деле это то же самое, что делать

int zippo[8]

С математикой, необходимой для 2D-адресации, обработанной для вас компилятором.

Подробнее см. в этом руководстве по массивам на С++.

Это совсем не так:

int** zippo

или

int* zippo[4]

В этом случае вы создаете массив из четырех указателей, которые могут быть выделены для других массивов.

Ответ 2

Когда выражение массива появляется в большинстве контекстов, его тип неявно преобразуется из "N-element array of T" в "указатель на T", а его значение устанавливается так, чтобы указывать на первый элемент в массиве. Исключения из этого правила заключаются в том, что выражение массива является операндом операторов sizeof или address-of (&), или когда массив является строковым литералом, который используется в качестве инициализатора в объявлении.

Таким образом, выражение zippo "распадается" от типа int [4][2] (4-элементный массив из 2-х элементовных массивов int) до int (*)[2] (указатель на 2-элементный массив int). Аналогично, тип zippo[0] равен int [2], который неявно преобразуется в int *.

Учитывая объявление int zippo[4][2], в следующей таблице показаны типы различных выражений массива с участием zippo и любых неявных преобразований:

Expression    Type            Implicitly converted to  Equivalent expression
----------    ----            -----------------------  ---------------------
zippo         int [4][2]      int (*)[2]               
&zippo        int (*)[4][2]       
*zippo        int [2]         int *                    zippo[0]
zippo[i]      int [2]         int *
&zippo[i]     int (*)[2]                               
*zippo[i]     int                                      zippo[i][0]
zippo[i][j]   int
&zippo[i][j]  int *
*zippo[i][j]  invalid

Обратите внимание, что zippo, &zippo, *zippo, zippo[0], &zippo[0] и &zippo[0][0] все имеют одинаковое значение; все они указывают на базу массива (адрес массива совпадает с адресом первого элемента массива). Однако типы различных выражений различаются.

Ответ 3

zippo не является указателем. Это массив значений массива. zippo и zippo[i] для i в 0..4 могут "разлагаться" на указатель в определенных случаях (в частности, в контекстах значений). Попробуйте выполнить печать sizeof zippo для примера использования zippo в контексте без значения. В этом случае sizeof сообщит размер массива, а не размер указателя.

Имя массива в контекстах значений распадается на указатель на его первый элемент. Таким образом, в контексте значений zippo совпадает с &zippo[0] и, следовательно, имеет тип "указатель на массив [2] of int"; *zippo, в контексте значений это то же самое, что и &zippo[0][0], то есть "указатель на int". Они имеют одинаковое значение, но разные типы.

Я рекомендую прочитать Массивы и указатели для ответа на ваш второй вопрос. Указатели имеют одинаковое "значение", но указывают на разное количество пространства. Попробуйте напечатать zippo+1 и *zippo+1, чтобы увидеть это более четко:

#include <stdio.h>

int main(void)
{
    int zippo[4][2] = { {2,4}, {6,8}, {1,3}, {5,7} };
    printf("%lu\n", (unsigned long) (sizeof zippo));
    printf("%p\n", (void *)(zippo+1));
    printf("%p\n", (void *)(*zippo+1));
    return 0;
}

Для моего запуска он печатает:

32
0xbffede7c
0xbffede78

Сообщив, что sizeof(int) на моей машине равен 4, а второй и третий указатели не равны по значению (как и ожидалось).

Кроме того, спецификатор формата "%p" нуждается в void * в функциях *printf(), поэтому вы должны указывать свои указатели на void * в ваших вызовах printf() (printf() - это вариационная функция, поэтому компилятор может " t сделайте автоматическое преобразование для вас здесь).

Изменить: Когда я говорю, что массив "распадается" на указатель, я имею в виду, что имя массива в контексте значения эквивалентно указателю. Таким образом, если у меня есть T pt[100]; для некоторого типа T, то имя pt имеет тип T * в контекстах значений. Для операторов sizeof и унарных & имя pt не сводится к указателю. Но вы можете сделать T *p = pt; — это совершенно верно, потому что в этом контексте pt имеет тип T *.

Обратите внимание, что это "разложение" происходит только один раз. Итак, скажем, у нас есть:

int zippo[4][2] = { {2,4}, {6,8}, {1,3}, {5,7} };

Затем zippo в контексте значения распадается на указатель типа: указатель на массив [2] из int. В коде:

int (*p1)[2] = zippo;

тогда как

int **p2 = zippo;

выведет предупреждение о несовместимости указателей.

С zippo, как указано выше,

int (*p0)[4][2] = &zippo;
int (*p1)[2] = zippo;
int *p2 = zippo[0];

все допустимы. Они должны печатать одно и то же значение при печати с помощью printf("%p\n", (void *)name);, но указатели отличаются тем, что они указывают на всю матрицу, строку и одно целое число соответственно.

Ответ 4

Важно то, что int zippy[4][2] не является тем же типом объекта, что и int **zippo.

Так же, как int zippi[5], zippy - это адрес блока памяти. Но компилятор знает, что вы хотите обратиться к восьми ячейкам памяти, начиная с zippy с двухмерным синтаксисом, но хотите адресовать пять мест памяти, начиная с zippi с одномерным синтаксисом.

zippo совсем другое. Он содержит адрес блока памяти, достаточно большой, чтобы содержать два указателя, и если вы укажете их на некоторые массивы целых чисел, вы можете разыменовать их с помощью синтаксиса доступа к двумерному массиву.

Ответ 5

Очень хорошо объяснил Рид, я добавлю еще несколько моментов, чтобы упростить его, когда мы обращаемся к zippo или zippo[0] или zippo[0][0], мы все еще имеем в виду тот же базовый адрес массива zippo. Причиной того, что массивы всегда являются непрерывным блоком памяти, а многомерные массивы - это многократные одномерные массивы, которые постоянно размещаются.

Когда вам нужно увеличивать каждую строку, вам нужен указатель int *p = &zippo[0][0], а выполнение p++ увеличивает указатель на каждую строку. В вашем примере id его массив 4 X 2, при выполнении p++ его, указатель в настоящее время указывает на второй набор из 4 элементов.