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

int arr[6][8];
int *ptr = &arr[0][0];
*(ptr+8) = 12; // arr[1][0]
printf("%d\n", arr[1][0]);

Теперь ... я понимаю, почему этот указатель указывает на эту ячейку, но если бы мне пришлось использовать идентификатор arr, я бы сделал это таким образом

*(*(arr+1)) = 12; // arr[1][0]

Но я не понимаю, почему я не могу сказать иначе

*(*(arr+8)) = 12;

Может быть, я настолько плохо разбираюсь в указателях и двумерных массивах, но мне бы очень хотелось понять, как работают подобные вещи.

Заранее спасибо за ответы!

0
std124_lf 10 Окт 2021 в 01:28

2 ответа

Лучший ответ

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

После int arr[6][8]; arr представляет собой массив из 6 массивов по 8 int. Если каждый int использует четыре байта, тогда весь массив использует 6 • 8 • 4 = 192 байта в памяти, а sizeof arr будет оценивать как 192, потому что arr - это массив , а sizeof дает размер его операнда.

Когда вы используете arr в выражении, оно будет автоматически преобразовано в указатель на свой первый элемент, за исключением случаев, когда он является операндом sizeof или унарного &. 1 Поскольку arr является массивом массивов, его первый элемент также является массивом. Первым элементом arr является arr[0], который представляет собой массив из 8 int. Итак, в arr+1 arr преобразуется в указатель на свой первый элемент. Этот указатель - &arr[0], адрес arr[0].

Когда к указателю добавляется целое число, скажем n , результат указывает на n дополнительных элементов в массиве. Итак, когда мы добавляем 1 к &arr[0], мы получаем &arr[1]. Таким образом, в arr+1 arr автоматически преобразуется в &arr[0], производя &arr[0]+1, а затем сложение производит &arr[1].

Таким образом, arr+1 равно &arr[1], что также является местом, где находится arr[1][0]. (Обратите внимание, что хотя &arr[1] и &arr[1][0] указывают на одно и то же место в памяти, они имеют разные типы, поэтому компилятор обрабатывает их по-разному при выполнении с ними арифметических операций.)

После int *ptr = &arr[0][0]; ptr, конечно, указывает на arr[0][0]. Тогда ptr+8 указывает на то, где был бы arr[0][8], если бы существовал такой элемент. Конечно, нет, поскольку arr[0] имеет только элементы от arr[0][0] до arr[0][7]. ptr+8 указывает на один за arr[0][7].

Эта арифметика указателей определяется стандартом C; вы можете указать «один за последним элементом». Однако это всего лишь указатель-заполнитель, полезный для арифметических операций и сравнений. Стандарт C не применяется при разыменовании указателя с помощью *(ptr+8).

Мы знаем, что arr[1][0] находится там, где гипотетически может быть arr[0][8]. Однако стандарт C не дает нам правил, согласно которым мы определенно можем использовать *(ptr+8) для доступа к arr[1][0]. Таким образом, этот код не имеет поведения, определенного стандартом C. Однако многие компиляторы будут рассматривать его как доступ к arr[1][0].

Как вы заметили, *(*(arr+1)) можно использовать для доступа к arr[1][0]. Это работает следующим образом:

  • arr автоматически преобразуется в &arr[0].
  • Добавление 1 дает &arr[1].
  • * разыменования &arr[1], производящие arr[1].
  • arr[1] - это массив, поэтому он автоматически преобразуется в указатель на свой первый элемент. Это производит &arr[1][0].
  • * разыменования &arr[1][0], производящие arr[1][0].

Напротив, *(*(arr+8)) не работает для доступа к arr[1][0]:

  • arr автоматически преобразуется в &arr[0].
  • Добавление 8 дает &arr[8].

Теперь мы далеко за пределами того, где заканчивается arr. arr содержит только элементы от arr[0] до arr[5], так что arr[8] находится за пределами конца.

Обратите внимание, что добавление 8 к &arr[0] перемещает указатель на 8 элементов массива arr , а не на 8 элементов массива arr[0]. То есть он переместил его на 8 массивов по 8 int, а не на 8 int. Это потому, что тип &arr[0] - это «указатель на массив из 8 int», а не «указатель на int».

Когда вы добавляете 8 к «указателю на int», вы перемещаете его на 8 int. Когда вы добавляете 8 к «указателю на массив из 8 int», вы перемещаете его на 8 массивов из 8 int.

Сноска

1

2
Eric Postpischil 10 Окт 2021 в 01:25

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

int array[2][3] = {0, 1, 2, 3, 4, 5}; // array declared & initialized
int *ptr; // pointer is declared but not initialized (is not assigned a value)
// above, pointer "ptr" points to some random memory address

ptr = &array[0][0]; // pointer is assigned to point to the starting element of the array
ptr = array; // same as above; pointer is assigned to point to the starting element of the array

// "ptr" is the address of the memory the pointer points to
// "*ptr" is the integer value stored in the memory address the pointer points to
// if the array is declared as "array[m][n]", then;
// "*(ptr + (i * n) + j)" is equivalent to "array[i][j]" i.e. the value
// "(ptr + (i * n) + j)" is equivalent to "&array[i][j]" i.e. the memory address

@ Джон Боллинджер прав! На самом деле он прав. Имена массивов в C не являются указателями, как мы ожидали. Код ниже объясняет:

// array declared below on stack
int a[3] = {1, 2, 3};

// same array with same values stored on heap
int *b = malloc(3 * sizeof(int));
*(b + 0) = 1;
*(b + 1) = 2;
*(b + 2) = 3;

printf("starting address of array a is %p\n", a);
printf("when treated like a pointer    %p\n", &a);
printf("see, they are same!!!\n");
printf("\n");
printf("starting address of array b is %p\n", b);
printf("memory address of the pointer  %p\n", &b);
printf("see, they are different!!!\n");
-1
ssd 10 Окт 2021 в 00:29