再论数组和指针

在前面提过,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)。


1. 大多数实现根本不处理这个问题,因为大多数实现不检查指针。那些做这些检查的实现,可能需要为每个对象分配一个额外的字节或字——或者更有效率地,为一个区块对象中‘最后’的那个对象——以让一个‘超出最后一个’指针能指向某个地方。


(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