对于数组而言,下标运算是随机读写的一种方式,也是最常用的方式。但是有很多教材(尤其是国内教材)一上来就说数组名就是指针,这是不对的。而且对于数组的下标访问背后是有一套规则的,熟悉这些规则可以在一些复杂语义的情况下分析出代码的实际含义。
假如现在我们有数组x,含有6个int型的元素:
1 | int x[6]={1,2,3,4,5,6}; |
对于x,我们可以对其进行下标访问:
1 | x[2]==3; |
有一个问题是:在我们进行下标操作的时候,背后究竟执行了什么?它是如何访问到数组x下标为2(第三个)元素的。
或许你也见过另外一种奇葩的数组下标访问方式:
1 | 2[x]==x[2]; |
ISO/IEC 14882:2014(E): The subscript operator [] is interpreted in such a way that E1[E2] is identical to *((E1)+(E2)).Because of the conversion rules that apply to +, if E1 is an array and E2 an integer, then E1[E2] refers to the E2-th member of E1. Therefore, despite its asymmetric appearance, subscripting is a commutative operation.
意味着数组的下标访问是以+
和*
(解引用)操作组合来说实现的。
当我们x[2]
的时候,会被转换成*(x+2)
。
由加法交换律可得:
1 | *(x+2)==*(2+x); |
即:
1 | x[2]==2[x]; |
进而推广可得:
1 | const int j=2; |
但是这里还有一个问题是:数组名是什么。看起来数组名像是指向数组首个元素的指针,但是这个不正确的(或者说是不严谨的)。
先说结论:当数组名出现在一个表达式中时,它被转换为一个指向首个元素(或多维数组的子对象)的指针。
1 | int x[3][5]={{1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15}}; // Here x is a 3 × 5 array of integers. |
可以从中间代码的角度来分析一下此处发生的事情:
1 | // 数组的初始化 |
可以看到,都是一层层的指针。
当我们将x赋值给一个指针对象时:
1 | auto xp=x; |
其中间代码为:
1 | %9 = getelementptr inbounds [3 x [5 x i32]], [3 x [5 x i32]]* %6, i32 0, i32 0 |
When x appears in an expression, it is converted to a pointer to (the first of three) five-membered arrays of integers.
即x
出现在表达式中会被转换为指向x[0]
对象(也就是一个含有五个整型元素的数组)的指针。
In the expression x[i] which is equivalent to *(x+i), x is first converted to a pointer as described; then x+i is converted to the type of x, which involves multiplying i by the length of the object to which the pointer points, namely five integer objects. The results are added and indirection applied to yield an array (of five integers), which in turn is converted to a pointer to the first of the integers.
这意味这数组名不是指针,而是代表一个数组对象,但是其可以在表达式中被隐式地转换为指向首个元素(对象)的指针。
由一致性原则(consistent rule)
可以将这个概念推广至多维数组(multidimensional arrays)
.
If there is another subscript the same argument applies again; this time the result is an integer.
即:
1 | &x==&x[0]; |
If E is an n-dimensional array of rank i×j×…×k,then E appearing in an expression that is subject to the array-to-pointer conversion (4.2) is converted to a pointer to an (n −1)-dimensional array with rank j×…×k. If the * operator, either explicitly or implicitly as a result of subscripting, is applied to this pointer, the result is the pointed-to (n − 1)-dimensional array, which itself is immediately converted into a pointer.
1 | const int i=2,j=3,k=4; |