入门客AI创业平台(我带你入门,你带我飞行)
博文笔记

c语言:为什么不进行下标检查,总结下指针和数组

创建时间:2014-10-14 投稿人: 浏览次数:1326

假设:

int array[ 10 ];

int *ap = array + 2;


ap[ -1 ]:下标引用就是间接访问表达式,只要将它转换成那种形式对它进行求值即可。ap只想第三个元素(下标为2),所以使用偏移量-1使我们得到它的前一个元素,也就是 array[ 1 ]。


ap[ 9 ]: 表达式看上去正常,但是实际上存在问题。它对等的表达式是 ap[ 11 ],表达式的结果是一个指针表达式,但是它所指向的位置越过了数组的右边界,根据标准它是非法的,但是很少由编译器报错。标准表示它的行为是未定义的,但在绝大多数机器上,它将访问哪个碰巧存储于数组最后一个元素后面第二个位置的值,我们没有办法预测它的值。因此这个表达式将访问某个任意变量的值,这个结果估计不是你想要的。


最后这两个例子说明了:为什么下标检查在c是一件很困难的事情。标准并没有提出这个要求,最早的c编译器并没有检查下标,最新的也不会检查。


因为下标引用可以用于任意的指针,而不仅仅是数组名。作用于指针的下标引用的有效性既依赖于该指针当时恰好指向的内容,也依赖于下标的值。


C的下标检查所涉及的开销,比我们刚开始想的要多。编译器必须在程序中插入指令,证实下标表达式的结果所引用的元素和指针表达式所指向的元素属于同一个数组。

这个比较操作需要程序中所有数组的位置和长度方面的信息,这将占用一些空间。当程序运行时,这些信息必须进行更新,以反映自动和动态分配的数组,这又将占用一定的时间。因此,即使是那些提供了下标检查的编译器通常也会提供一个开关,允许去掉下标检查。


提供一个有趣的例子,假定下面表达式所处的上下文环境和前面的相同,它的意思是什么?

2[ array ]

它的答案:合法!转换成对等的间接访问表达式:

*( 2 + ( array ) )

内层括号是冗余,可以去掉,所以它和下面的是等价的:

*( array + 2 )

这个诡异技巧可行,因为c实现下标的方法,对于编译器而言,两种形式并无差别,但是,你绝不该写成 2[ array ] ,因为它大大影响了可读性。



再次总结一下:


在绝大多数表达式中,数组名的值是指向数组第一个元素的指针。只有两个例外:一,sizeof 返回整个数组所占用的byte的大小 而不是一个指针所占用的byte的大小。二,单目操作符 & 返回一个指向数组的指针,而不是一个指向数组第一个元素的指针的指针。


除了优先级不同之外,下标表达式 array[ value ] 和间接访问表达式 *( array + value ) 是一样的,因此,下标不仅可以用于数组名,也可以用于指针表达式中。


这样以来,编译器很难检查下标的有效性!指针表达式可能比下标表达式效率更高,但是下标表达式决不能比指针表达式效率更高。 但是,以牺牲程序可维护性为代价换来的程序运行时的效率的提高可不是好主意。


指针和数组并不相等。数组的属性和指针的属性大相径庭。当我们声明一个数组时,它同时也分配了一些内存空间,用于容纳数组元素。但是,当我们声明一个指针时,它只分配了用于容纳指针本身的空间。


当数组名作为函数参数传递时,实际传递给函数的是一个指向数组第一个元素的指针。 函数所接收到的参数实际上是原函数参数的一份拷贝,所以函数可以对其进行操作而不会影响实际的参数。 但是,对指针参数进行间接访问(×)操作,允许修改原先数组的元素。数组形参既可以声明为数组,也可以声明为指针。 这两种声明形式只有当它们作为函数的形参时才相等。


比如:

对于一位数组,char temp[ 10 ];  可以写成 void fun( char *temp ){ }或者 void fun( char temp[] ){ } 

对于二维数组,char temp[ 3 ][ 10 ]; 可以写成 void fun( char temp[ ][ 10 ] ){ } 或者 void fun( char (*temp)[ 10 ] ) { }

注意:对于多为数组,只能将第一维写成 (数组 / 指针) 的形式,编译器必须知道第二个以及以后各维的长度,才能对各下标进行求值,因此原型中必须声明这些维的长度,第一维的长度不重要,因为在计算下标值的时候用不到它。


当数组名作为函数的参数传递时,实际传递给函数的是一个指向数组第一个元素的指针。函数所接收到的参数实际上是原参数的一根拷贝,所以函数可以对其进行操作而不会影响实际的参数。但是,对指针参数执行间接访问(×)操作允许函数修改原先的数组元素。数组形参既可以声明为数组,也可以声明为指针,见上面。这两种形式只有当它们作为函数的形参时才是等价的。


数组也可以用初始值列表进行初始化,初始值列表就是由一对花括号包围的一组值。静态变量(包括数组)在程序载入到内存时得到初始值。自动变量(包括数组)每次当执行流进入它们声明所在的代码块时都要使用隐式赋值语句重新初始化。 如果初始值列表包含的值的个数少于数组元素的个数,数组最后几个元素就用缺省值进行初始化。 如果一个被初始化的数组的长度在声明中未给出,编译器将使用这个数组的长度设置为刚好能够容纳初始列表中所有值的长度。字符数组也能够用一种很像字符串常量的方式进行初始化。


多维数组实际上是以为数组的一种特型,就是它的每一个元素本身也是一个数组。多维数组中的元素根据“行”主序进行存储,也就是最右边的下标率先变化。 多维数组名的值是一个指向它第一个元素的指针,也就是一个指向数组的指针。对该指针进行运算将根据它所指向数组的长度对操作数进行调整。 多维数组的下标引用也是指针表达式。 当一个多维数组名作为参数传递给一个函数时,它所对应的函数形参的声明中必须显示指明第二维(和接下去所有维)的长度。 由于多维数组实际上是复杂元素的一维数组,一个多维数组的初始化列表就包含了这些复杂元素的值。 这些值的每一个都可能包含嵌套的初始值列表,由数组各维的长度决定。 如果多维数组的初始化列表是完整的,它的内层花括号可以省略。 在多维数组的初始值列表中,只有第一维的长度会被自动计算出来。


我们还可以创建指针数组。 字符串的列表可以以矩阵的形式存储,也可以以指向字符串常量的指针数组形式存储。 在矩阵中,每行必须与最长字符串的长度一样,但是它不需要任何指针。 指针数组本身要占用空间,但是每个指针所指向的字符串所占用的内存空间就是字符串本身的长度。

阅读更多
声明:该文观点仅代表作者本人,入门客AI创业平台信息发布平台仅提供信息存储空间服务,如有疑问请联系rumenke@qq.com。
  • 上一篇:没有了
  • 下一篇:没有了
未上传头像