还有一种说法是,在编写数组算法时,使用指针比使用数组“更有效率”。
这个颇为人们所接受的说法在通常情况下是错误的。使用现代的产品质量优化的编译器,一维数组和指针引用所产生的代码并不具有显著的差别。不管怎样,数组下标是定义在指针的基础上的,所以优化器常常可以把它转换为更有效率的指针表达形式,并生成相同的机器指令。让我们再看一下数组/指针这两种方案,并把初始化从循环内部的访问中分离出来。
int a [ 10] ,*p, i;
变量a[i]可以用图9-2所示的各种方法来访问,效果完全一样。
即使编译器使用的是较原始的翻译方法,两者产生不一样的代码,用指针迭代一个一维数组常常也并不比直接使用下标迭代一个一维数组来得更快。不论是指针还是数组,在连续的内存地址上移动时,编译器都必须计算每次前进的步长。计算的方法是偏移量乘以每个数组元素占用的字节数,计算结果就是偏移数组起始地址的实际字节数。步长因子常常是2的乘方(如int是4个字节,double是8个字节等),这样编译器在计算时就可以使用快速的左移位运算,而不是相对缓慢的加法运算。一个二进制数左移3位相当于它乘以8。 如果数组中的元素的大小不是2的乘方(如数组的元素类型是一个结构),那就不能使用这个技巧了。
然而,迭代一个int数组是人们最容易想到的。如果一个经过良好优化的编译器进行代码分析,并把基本变量放在高速的寄存器中来确认循环是否继续,那么最终在循环中访问指针和数组所产生的代码很可能是相同的。
在处理一维数组时,指针并不见得比数组更快。C语言把数组下标改写成指针偏移量的根本原因是指针和偏移量是底层硬件所使用的基本模型。
上面这些例子显示了不同的备选方案经过翻译后所产生的中间代码。如果釆用优化措施,中间代码可能跟这里显示的不一样。RO、R1等代表CPU的寄存器。在图中,我们用
R0存储p的左值 R1存储a的左值或p的右值
R2存储i的左值 R3存储i的右值
[R0]表示间接载入或写入,其地址就是寄存器的内容(这是许多汇编语言所使用的一个普通概念)。 “可以提到循环外”表示这个数据不会被循环修改,在每次循环时可不必执行该语句,可以加快循环的速度。
“作为函数参数的数组名”等同于指针
规则3也需要进行解释。
首先,让我们回顾一下the C programming language中所提到的一些术语。
标准规定作为“类型的数组”的形参的声明应该调整为“类型的指针”。在函数形参定义这个特殊情况下,编译器必须把数组形式改写成指向数组第一个元素的指针形式。编译器只向函数传递数组的地址,而不是整个数组的拷贝。不过,现在让我们重点观察一下数组,隐性转换意味着三种形式是完全等同的。因此,在my_function()的调用上,无论实参是数组还是真的指针都是合法的。
my_function(int *turnip) { .
my—function(int turnip[]) {
my一function(int turnip[200]) {