在前面提过,C有一个关于数组和指针的非常重要的规则。它这样说,在值上下文,一个‘T类型数组’的对象变成一个指向数组首元素的‘T类型指针’的值。因为其结果是一个值,而非一个对象。该规则在任意指定子表达式上仅应用一次。然而,如果该‘指向T的指针’指针值指向另一个数组,即是说,通过那个指针解引用的结果也是一个数组对象,那个规则能(或不能)再次应用(依赖于是否那个对象是‘一个值上下文’)。
到目前为止,理解所有这些的最好的方法,是检查几个真实对象的图例。这里有一个这样的图,其中我们的C代码中至少有六个对象。这些对象之一是包含四个char的数组,或者像我喜欢说的那样,具有‘四个char的数组’类型。这个数组保存一个字符串"abc"。另一个是一个设为123的普通int对象。这两个对象在图中没有意义,只是把它弄得更乱。它们的大小相同,表示该实现中sizeof(int)==4。
余下的四个对象都是重要的。最大的框是一个‘四个两个int数组的数组’的对象,或者int [4][2]。它的元素初始化为从0到7的值。有三个指针,每个的类型不同。
+---------->A---------+---------+ o --------+ | 0 | 1 | +------>|B- - - -B| | int (*A)[4][2]; | +-->|C- - - - + - - - -C| | | +---------+---------+ | | | 2 | 3 | o ------------+ | | | | | | | | int *B; | +---------+---------+ | | 4 | 5 | | | | | o ----------------+ | | | +---------+---------+ int (*C)[2]; | 6 | 7 | | | | | | | +---------+---------A
这个特别的图例可以在进入这样的函数时产生:
void f(void) { int matrix[4][2] = { {0,1}, {2,3}, {4,5}, {6,7} }; int (*A)[4][2] = &matrix; int *B = &matrix[0][0]; int (*C)[2] = &matrix[0]; char s[] = "abc"; int i = 123; /* code goes here */ }
在这个图中指针间的不同并不在于‘所指之处’,而是所指多少。三个指针肯定能让你在matrix[0][0]中定位到值0,如果转换这些指针为‘字节地址’并且在printf中用%p格式指令输出它们,这三个极可能产生相同的输出(在一个典型的现代计算机上)。但是int *B指针只指向一个单一单元,指针int (*C)[2]指向两个int的实体,指针int (*A)[4][2]指向整个数组。
这些不同之处影响指针算术和一元解引用操作符*的结果。因为B指向单个int,B+1向前移动单个int。*(B + 1)恰好是下一个int,它的值是1。同样sizeof *B恰好是sizeof(int)(可能是4)。
因为指针C指向‘两个int的数组’实体,C+1向前移动这个实体宽度。结果是一个指向(2,3)的指针。因为一个对象的解引用操作符的结果,*(C + 1)是那个整体,它在那个规则下可能失败。如果它没有失败,反之那个对象将变成一个指向其首元素的指针,例如其int当前是2。如果它在规则下不成功——例如sizeof *(C + 1)将置于对象上下文——它将保持整个数组对象。这表示sizeof *(C + 1)(当然同样对于sizeof *C)是sizeof(int[2])(可能是8)。
最后指针A指向整个数组,A+1向前移动整个数组宽度。这将指向不存在的对象。ANSI C允许计算这样的指针,但是不能将它解引用[1]。因为*(A + 1)本身是一个错误,可能最好是应注意*A是整个数组,因此sizeof *A是sizeof(int[4][2],或者4*2*sizeof(int)——这种情况下,极有可能是32,如果sizeof(int)是4)。
(This Chinese translation isn't confirmed by the author, and it isn't for profits.)
Translator : jhlicc@gmai1.c0m
Origin : http://www.torek.net/torek/c/pa.html