Адрес указателя в многомерном массиве C - PullRequest
28 голосов
/ 05 января 2010

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

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

Ответы [ 5 ]

32 голосов
/ 05 января 2010

Когда выражение массива появляется в большинстве контекстов, его тип неявно преобразуется из «N-элементного массива 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] имеют одинаковое значение; все они указывают на базу массива (адрес массива совпадает с адресом первого элемента массива). Однако типы различных выражений различаются.

31 голосов
/ 05 января 2010

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

В вашем случае делаете:

int zippo[4][2]

Это действительно то же самое, что и

int zippo[8]

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

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

Это сильно отличается от того, что вы делаете:

int** zippo

или

int* zippo[4]

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

5 голосов
/ 05 января 2010

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

Имя массива в значениях распадается на указатель на его первый элемент. Таким образом, в контексте значения zippo совпадает с &zippo[0] и, следовательно, имеет тип «указатель на массив [2] из 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 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);, но указатели отличаются тем, что они указывают на всю матрицу, строку и одно целое число соответственно.

2 голосов
/ 06 января 2010

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

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

2 голосов
/ 05 января 2010

Здесь важно то, что int zippy[4][2] не тот же тип объекта, что и int **zippo.

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

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

...