首先我们需要澄清一件重要的事情:数组不是指针。数组类型和指针类型是完全不同的类型,并且编译器会对它们进行不同的处理。
混淆发生的地方在于C语言如何处理数组表达式。N1570:
6.3.2.1 左值、数组和函数设计者
...
3 对于一个具有数组类型的表达式,在以下情况下除外:作为 sizeof
运算符、_Alignof
运算符或一元 &
运算符的操作数,或者是用于初始化数组的字符串字面量。此时,该表达式被转换为一个类型为“指向数组对象的第一个元素的指针”的表达式,而不是左值。如果该数组对象有 register 存储类别,则其行为未定义。
让我们来看一下以下声明:
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
)。"after" 那行应该显示三个不同的值:
0x10008000
,
0x10008002
(基地址加上
sizeof(int)
),以及
0x10008014
(基地址加上
sizeof(int [10])
)。
现在让我们回到上面第二段中说的:在大多数情况下,数组表达式会被转换为指针类型。让我们看一下下标表达式
arr[i]
。由于表达式
arr
既不是出现在
sizeof
或
&
操作符的操作数位置上,也不是用作初始化另一个数组的字符串字面量,所以它的类型从 "10 个元素的 int 数组" 转换成了 "int 指针",并且下标操作被应用于这个指针值。实际上,当你看 C 语言定义时,会看到以下语言:
6.5.2.1 数组下标
...
2. 后缀表达式后跟方括号内的表达式 [] 是数组对象的带下标的指定。下标运算符 [] 的定义是 E1[E2] 等同于 (*((E1)+(E2)))。由于适用于二元 + 运算符的转换规则,如果 E1 是数组对象(或等效地,是数组对象的初始元素的指针),而 E2 是整数,则 E1[E2] 指代 E1 的第 E2 个元素(从零开始计数)。
实际上,这意味着你可以将下标运算符应用于指针对象,就好像它是一个数组一样。这就是为什么像下面这样的代码能够工作的原因:
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]
均为有效表达式,并且两者将产生相同的值。