Ответ 1
Давайте сначала извлеките важный материал: массивы не являются указателями. Типы массивов и типы указателей - это совершенно разные вещи и обрабатываются компилятором по-разному.
Если возникает путаница, то, как C обрабатывает выражения массива. N1570:
6.3.2.1 Lvalues, массивы и обозначения функций
...
3 За исключением случаев, когда это операнд оператораsizeof
, оператор_Alignof
или унарный оператор&
или строковый литерал, используемый для инициализации массива, выражение, которое имеет type '' типа типа преобразуется в выражение с указателем типа '', чтобы набирать такие точки к исходному элементу объекта массива и не является значением lvalue. Если объект массива имеет зарегистрируйте класс хранения, поведение не определено.
Посмотрите на следующие объявления:
int arr[10] = {0,1,2,3,4,5,6,7,8,9};
int *parr = arr;
arr
- это 10-элементный массив int
; это относится к непрерывному блоку памяти, достаточному для хранения значений 10 int
. Выражение arr
во втором объявлении имеет тип массива, но поскольку он не является операндом &
или sizeof
и не является строковым литералом, тип выражения становится "указателем на int
", а значение - адрес первого элемента или &arr[0]
.
parr
- указатель на int; это относится к блоку памяти, достаточно большому, чтобы удерживать адрес одного объекта int
. Он инициализируется, чтобы указать на первый элемент в arr
, как объяснялось выше.
Здесь представлена гипотетическая карта памяти, показывающая взаимосвязь между ними (предполагая 16-битные int и 32-разрядные адреса):
Object Address 0x00 0x01 0x02 0x03 ------ ------- ---------------------- arr 0x10008000 0x00 0x00 0x00 0x01 0x10008004 0x00 0x02 0x00 0x03 0x10008008 0x00 0x04 0x00 0x05 0x1000800c 0x00 0x06 0x00 0x07 0x10008010 0x00 0x08 0x00 0x09 parr 0x10008014 0x10 0x00 0x80 0x00
Типы имеют значение для таких вещей, как sizeof
и &
; sizeof arr == 10 * sizeof (int)
, который в этом случае равен 20, тогда как sizeof parr == sizeof (int *)
, который в этом случае равен 4. Аналогично, тип выражения &arr
равен int (*)[10]
или указателю на 10-элементный массив int
, тогда как тип &parr
равен int **
или указателю на указатель на int
.
Обратите внимание, что выражения arr
и &arr
будут давать одно и то же значение (адрес первого элемента в arr
), но типы выражений различны (int *
и int (*)[10]
, соответственно). Это имеет значение при использовании арифметики указателя. Например, данный:
int arr[10] = {0,1,2,3,4,5,6,7,8,9};
int *p = arr;
int (*ap)[10] = &arr;
printf("before: arr = %p, p = %p, ap = %p\n", (void *) arr, (void *) p, (void *) ap);
p++;
ap++;
printf("after: arr = %p, p = %p, ap = %p\n", (void *) arr, (void *) p, (void *) ap);
строка "before" должна печатать одинаковые значения для всех трех выражений (на нашей гипотетической карте, 0x10008000
). Строка "после" должна показывать три разных значения: 0x10008000
, 0x10008002
(base plus sizeof (int)
) и 0x10008014
(base plus sizeof (int [10])
).
Теперь вернемся ко второму абзацу выше: выражения массива в большинстве случаев преобразуются в типы указателей. Посмотрим на выражение подстроки arr[i]
. Поскольку выражение arr
не является операндом либо sizeof
, либо &
, и поскольку он не является строковым литералом, используемым для инициализации другого массива, его тип преобразуется из "10-элементного массива int
" to "на int
", и операция индекса применяется к этому значению указателя. Действительно, когда вы смотрите на определение языка C, вы видите следующий язык:
6.5.2.1 Подписывание массива
...
2 Постфиксное выражение, за которым следует выражение в квадратных скобках [], является индексированным обозначением элемента объекта массива. Определение нижнего индекса [] состоит в том, что E1 [E2] идентично (* ((E1) + (E2))), Из-за правил преобразования, которые применяются к двоичному + оператору, если E1 является объектом массива (эквивалентно указателю на исходный элемент объекта массива) и E2 целое число, E1 [E2] обозначает E2 -й элемент E1 (с нуля).
В практическом плане это означает, что вы можете применить оператор индекса к объекту-указателю, как если бы он был массивом. Вот почему код вроде
int foo(int *p, size_t size)
{
int sum = 0;
int i;
for (i = 0; i < size; i++)
{
sum += p[i];
}
return sum;
}
int main(void)
{
int arr[10] = {0,1,2,3,4,5,6,7,8,9};
int result = foo(arr, sizeof arr / sizeof arr[0]);
...
}
работает так, как он делает. main
имеет дело с массивом int
, тогда как foo
имеет дело с указателем на int
, но оба могут использовать индексный оператор, как если бы они имели дело с типом массива.
Это также означает, что подтипирование массива является коммутативным: если a
является выражением массива, а i
является целочисленным выражением, a[i]
и i[a]
являются действительными выражениями, и оба значения будут иметь одинаковое значение.