C与指针 四 数组下标

现在考虑下面这个例子:
int a[10];
int b[10];
int *c;
c=&a[0];
       表达式&a[0]是一个指向数组第1个元素的指针。但那正是数组名本身的值,所以下面这条赋值语句和上面那条赋值语句所执行的任务是完全一样的:
c = a;
       这条赋值语句说明了为什么理解表达式中的数组名的真正含义是非常重要的。如果数组名表示整个数组,这条语句就表示整个数组被复制到一个新的数组。但事实上完全不是这样,实际被赋值的是一个指针的拷贝,〇所指向的是数组的第1个元素。因此,像下面这样的表达式:
b = a;

        是非法的。你不能使用赋值符把一个数组的所有元素复制到另一个数组。你必须使用一个循环,每次复制一个元素。

考虑下面这条语句:

a = c;

        c被声明为一个指针变量,这条语句看上去像是执行某种形式的指针赋值,把〇的值复制给a。但这个赋值是非法的:记住!在这个表达式中,a的值是个常量,不能被修改。

下标引用

* ( b + 3 )

        首先,b的值是一个指向整型的指针,所以3这个值根据整型值的长度进行调整。加法运算的结果是另一个指向整型的指针,它所指向的是数组第1个元素向后移3个整数长度的位置。然后,间接访问操作访问这个新位置,或者取得那里的值(右值),或者把一个新值存储于该处(左值)。
       这个过程听上去是不是很熟悉?这是因为它和下标引用的执行过程完全相同。除了优先级之外,下标引用和间接访问完全相同。例如,下面这两个表 达式是等:

array[subscript]

*(array+(subscript))

         既然你已知道数组名的值只是一个指针常量,你可以证明它们的相等性。在那个下标表达式中,子表达式subSCript首先进行求值。然后,这个下标值在数组中选择一个特定的元素。在第2个表达式中,内层的那个括号保证子表达式subscript像前一个表达式那样首先进行求值。经过指针运算,加法运算的结果是一个指向所需元素的指针。然后,对这个指针执行间接访问操作,访问它指向的那个数组元素。
        在使用下标引用的地方,你可以使用对等的指针表达式来代替。在使用上面这种形式的指针表达式的地方,你也可以使用下标表达式来代替。这里有个小例子,可以说明这种相等性。

int array[10];

int *ap = array + 2;

记住,在进行指针加法运算时会对2进行调整。运算结果所产生的指针叩指向&〇^^[2],如下所示:


在下面各个涉及ap的表达式中,看看你能不能写出使用array的对等表达式。

        Ap         这个很容易,你只要阅读它的初始化表达式就能得到答案:array+2。另外,&array[2]也是与它对等的表达式。
       *ap         这个也很容易,间接访问跟随指针访问它所指向的位置,也就是array[2]。你也可以这样写:*(array+2)。
       ap[0]      “你不能这样做,ap不是一个数组!”如果你是这样想的,你就陷入了“其他语言不能这样做”这个惯性思维中了。记住,C的下标引用和间接访问表达式是一样的。在现在这种情况 下,对等的表达式是*(ap+(0)),除去0和括号,其结果与前一个表达式相等。因此,它的答案和上 一题相同:array[2]。
       ap+6       如果ap指向array[2],这个加法运算产生的指针所指向的元素是array[2]向后移动6个整数位置的元素。与它对等的表达式是array+8或&array[8]。
       *ap+6     小心!这里有两个操作符,哪一个先执行呢?是间接访问。间接访问的结果再与6相加,所以这个表达式相当于表达式array[2]+6。
       *(
ap+6)   括号迫使加法运算首先执行,所以我们这次得到的值是array[8]。注意这里的间接访问操作和下标引用操作的形式是完全一样的。

       ap[6]       把这个下标表达式转换为与其对应的间接访问表达式形式,你会发现它就是我们刚刚完成的那个表达式,所以它们的答案相同。
       &ap         这个表达式是完全合法的,但此时并没有对等的涉及array的表达式,因为你无法预测编译器会把ap放在相对于array的什么位置。
       ap[-l]       怎么又是它?负值的下标!下标引用就是间接访问表达式,你只要把它转换为那种形式并对它进行求值。ap指向第3个元素(就是那个下标值为2的元素)所以使用偏移量-1使我们得到它的前一个元素,就是array[1]。

       ap[9]        这个表达式看上去很正常,但实际上却存在问题。它对等的表达式是array[11],但问 题是这个数组只有10个元素。这个下标表达式的结果是一个指针表达式,但它所指向的位置越过了 数组的右边界。根据标准,这个表达式是非法的。.但是,很少有编译器能够检测到这类错误,所以 程序能够顺利地继续运行。但这个表达式到底干了些什么?标准表示它的行为是未定义的,但在绝 大多数机器上,它将访问那个碰巧存储于数组最后一个元素后面第2个位置的值。你有时可以通过 请求编译器产生程序的汇编语言版本并对它进行检查,从而推断出这个值是什么,但你并没有统一 的办法预测存储在这个地方的到底是哪个值。因此,这个表达式将访问(或者,如果作为左值,将 修改)某个任意变量的值。这个结果估计不是你所希望的。
        最后两个例子显示了为什么下标检查在C中是一项困难的任务。标准并未提出这项要求。最早的C编译器并不检查下标,而最新的编译器依然不对它进行检查。这项任务之所以很困难,是因为下标引用可以作用于任意的指针,而不仅仅是数组名。作用于指针的下标引用的有效性既依赖于该指针当时恰好指向什么内容,也依赖于下标的值。

        结果,C的下标检查所涉及的开销比你刚开始想象的要多。编译器必须在程序中插入指令,证实下标表达式的结果所引用的元素和指针表达式所指向的元素属于同一个数组。这个比较操作需要程序中所有数组的位置和长度方面的信息,这将占用一些空间。当程序运行时,这些信息必须进行更新,以反映自动和动态分配的数组,这又将占用一定的时间。因此,即使是那些提供了下标检查的编译器通常也会提供一个开关,允许你去掉下标检查。
        这里有一个有趣的,但同时也有些神秘和离题的例子。假定下面表达式所处的上下文环境和前面的相同,它的意思是什么呢?

2[array]

它的答案可能会令你大吃一惊:它是合法的。把它转换成对等的间接访问表达式,你就会发现它的有效性:

*(2 + ( array ))

        内层的那个括号是冗余的,我们可以把它去掉。同时,加法运算的两个操作数是可以交换位置的,所以这个表达式和下面这个表达式是完全一样的

*( array+ 2 )

        也就是说,最初那个看上去颇为古怪的表达array[2]是相等的。
        这个诡异技巧之所以可行,缘于C实现下标的方法。对编译器来说,这两种形式并无差别。但 是,你绝不应该编写2[array],因为它会大大影响程序的可读性。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值