Указатель на массив
Массив является отдельным типом в C. Это последовательные коллекции элементов заданного типа. В C двумерный массив фактически является массивом одномерных массивов. В вашем случае у вас есть массив [4] int [5]
(например, массивы из 4-5 элементов int
, обычно называемые двумерным массивом int
)
. как массив обрабатывается при доступе. При обращении к массиву он преобразуется в указатель на первый элемент. Стандарт C11 - 6.3.2.1 Другие операнды - L-значения, массивы и обозначения функций (p3) (обратите внимание на 4 исключения)
В случае одномерного массива это просто массив преобразуется в указатель на первый элемент массива (указатель просто int*
). В случае двумерного массива то же самое верно, массив преобразуется в указатель на первый элемент - но этот первый элемент является одномерным массивом с 5-целым числом. (указатель является указатель на массив из int [5]
, формально int (*)[5]
)
Вы можете передать двумерный массив (в вашем случае) в качестве параметра либо int grid[4][5]
, int grid[][5]
или для отражения того, что массив преобразован в указатель на первый элемент, int (*grid)[5]
. Ключ заключается в том, что вы всегда должны указывать количество элементов в конечном измерении для вашего массива (с дополнительными '*'
, разрешенными для обстоятельств, которые здесь не имеют значения) 5
(или количество элементов) должно быть integer constant
, которое известно во время компиляции, если не используется массив переменной длины (VLA), которые являются топи c для отдельного обсуждения.
То же правило, что при доступе массив преобразуется в указатель на его первый элемент применяется к каждому измерению в вашем массиве, будь то 2D-массив или 6D-массив. C11 Standard - 6.5.2.1 Подписка на массив (p3)
Кроме того, необходимо знать разницу между указателем на массив (например, int (*grid)[5]
) и массив указателей (например, int *grid[5]
). Скобки требуются из-за C Приоритет оператора , [..]
имеет более высокий приоритет, чем '*'
в этом случае, поэтому требуется, чтобы *grid
(в int *grid[5]
) оценивалось как указатель (вместо массива grid[5]
), заключенный в скобки (*grid)
. Таким образом, получается указатель на массив из int [5]
, (int (*grid)[5]
) вместо массив-указателей до int
(5 из них) с int *grid[5]
.
Указатель на указатель
Сравните это с указатель на указатель (например, int **
, обычно называемый двойным -указатель). У вас есть два уровня косвенности, представленные двумя **
. Сам указатель является одиночным указателем - к чему? (другой указатель , а не массив ). Как правило, вы будете использовать двойной указатель, сначала выделяя блок памяти для хранения некоторого числа указателей , например, когда вы динамически выделяете неизвестное количество выделенных объектов. Это может быть неизвестное количество строк с неизвестным числом столбцов int
, или это может быть неизвестное количество строк или неизвестное количество структур, и т. Д. c .. Ключ - ваш первый уровень точек косвенности в память, содержащую указатели .
Затем для каждого из доступных указателей вы можете выделить блок (например, в вашем случае удерживать 5 int
, а затем назначить начальный адрес этого блока памяти для вашего первого доступного указателя). Вы продолжаете выделять для своих столбцов (или строк или структур) и назначаете начальный адрес каждому из ваших доступных указателей в последовательности. Когда вы закончите, вы можете получить доступ к отдельным элементам в выделенной коллекции, используя ту же индексацию, что и для двумерного массива. Разница между такой коллекцией и двумерным массивом массивов заключается в том, что память, на которую указывает каждый указатель, не обязательно должна быть последовательной в памяти.
Разъединяя их
Ключ к знанию того, что использовать, - спросить " На что указывает мой указатель? " Указывает ли он на указатель? Или это указывает на массив? Если он указывает на другой указатель, то у вас есть указатель на указатель . Если указанная вещь является массивом, то у вас есть указатель на массив . При этом вы знаете, что вам нужно в качестве параметра.
Почему SegFault с int **
Тип управляет арифметикой указателя c. Напомним выше, int**
это указатель на указатель , так насколько велик указатель? (sizeof (a_pointer)
- обычно 8 байтов в x86_64 или 4 байта в x86). Таким образом, grid[1][0]
находится всего в одном указателе (8 байт) от grid[0][0]
. Что насчет указателя на массив ? Каждый шаг в первом индексе на sizeof (int[5])
отличается от первого. Таким образом, в случае массива 4x5 grid[1][0]
равно 5 * sizeof(int)
(20 байтов), кроме grid[0][0]
.
Таким образом, пытаясь получить доступ к вашему массиву массивов, используя int**
, начиная с grid[1][3]
(или grid[1][4]
в 32-битном поле), вы читаете один за концом 1-й строки ценностей. (у вас есть смещение на 8 байтов (один указатель на 8 байтов - пропускаем 2-целое), помещая вас непосредственно перед третьим целым числом в 1-й строке, затем смещая еще на 3 целых, помещая вас в то, что будет grid[0][5]
на один последнее значение в 1-й строке grid[0][4]
. (составляется с каждым шагом строки) Результат не определен, и может произойти все что угодно.
Когда вы передаете соответствующий указатель на массив каждое смещение индекса строки смещается на 20 байтов, помещая вас в начало следующего одномерного массива значений, поэтому итерации по каждому столбцу остаются в границах этого одномерного массива.
Продумайте это, и если у вас есть дополнительные вопросы, просто дайте мне знать, и я буду рад помочь вам.