使用指针进行内存访问比使用数组访问更有效率,但在编译器变得相对愚笨的过去可能是真的。只需查看高优化模式下gcc输出的一些代码就可以知道这不再成立了。有些代码很难理解,但一旦掌握,它的杰出之处就显而易见了。一个好的编译器将为指针和数组访问生成相同的代码,你应该不必担心那个级别的性能。编写编译器的人对目标架构的了解远远超过我们凡人。在优化代码时更应集中注意宏观层面(如算法选择等),并相信你的工具制造商会尽职尽责。
事实上,我很惊讶编译器没有优化整个
temp = a[0];
由于在下一行中用不同的值覆盖了temp
,并且a
没有以任何方式标记为volatile
,因此该行代码将被优化而消失。
我还记得很久以前有一个城市传说,关于最新的VAX Fortran编译器的基准测试(表明我很老),它比其竞争对手快出数个数量级。
原来是因为编译器发现基准计算结果在任何地方都没有使用,所以它将整个计算循环优化成不存在。因此运行速度大幅提高。
更新:优化后的代码之所以在您的特定情况下更有效率,是因为您找到位置的方式。 a
将在链接/加载时决定固定位置,并且对它的引用将同时被确定。因此,a [0]
或者 a [任何常量]
将在固定位置。
由于相同的原因,p
本身也将处于固定位置。但是,*p
(p
的内容)是可变的,因此需要额外的查找来找到正确的内存位置。
您可能会发现,拥有另一个变量x
(不是const
),并使用a [x]
也会引入额外的计算。
在您的评论中,您声明:
按照您的建议操作会导致通过数组进行内存访问的3条指令(获取索引,获取数组元素值,存储在临时变量中)。但我仍然看不到效率。:-(
我的回答是:您很可能无法通过使用指针来提高效率。现代编译器足以确定数组操作和指针操作可以转换为相同的底层机器代码。
实际上,在未启用优化的情况下,指针代码可能会更低效。请考虑以下转换:
int *pa, i, a[10]
for (i = 0
a[i] = 100
/*
movl $0, -16(%ebp)
L2:
cmpl $9, -16(%ebp)
jg L3
movl -16(%ebp), %eax
movl $100, -72(%ebp,%eax,4)
leal -16(%ebp), %eax
incl (%eax)
jmp L2
L3:
*/
for (pa = a
*pa = 100
/*
leal -72(%ebp), %eax
movl %eax, -12(%ebp)
L5:
leal -72(%ebp), %eax
addl $40, %eax
cmpl -12(%ebp), %eax
jbe L6
movl -12(%ebp), %eax
movl $100, (%eax)
leal -12(%ebp), %eax
addl $4, (%eax)
jmp L5
L6:
*/
从那个例子中,你可以看到指针示例更长,而且是不必要的。它多次将pa
加载到%eax
中,尽管pa
没有改变,实际上在%eax
中交替使用pa
和&(a[10])
。这里的默认优化基本上根本不存在。
当你升级到优化级别2时,你得到的代码是:
xorl %eax, %eax
L5:
movl $100, %edx
movl %edx, -56(%ebp,%eax,4)
incl %eax
cmpl $9, %eax
jle L5
对于数组版本:
leal -56(%ebp), %eax
leal -16(%ebp), %edx
jmp L14
L16:
movl $100, (%eax)
addl $4, %eax
L14:
cmpl %eax, %edx
ja L16
对于指针版本,我不会进行时钟周期分析(因为这太麻烦了,而且我基本上很懒),但我会指出一件事情。就汇编指令而言,两个版本的代码之间没有太大区别,考虑到现代CPU的运行速度,除非您要执行数十亿次此类操作,否则您不会注意到任何差异。我总是倾向于编写易读的代码,并且只在性能成为问题时才担心性能。
顺便说一句,您引用的那个声明:
5.3 指针和数组:指针版本通常会更快,但至少对于初学者来说,有些难以立即理解。
可以追溯到最早的K&R版本,包括我的古老的1978年版本,其中函数仍然是这样编写的:
getint(pn)
int *pn;
{
...
}
自从那时候以来,编译器已经走了很长的路。
a[i++]
与*p++
之间。我认为那时候与你的问题所问的是完全不同的。 - Johannes Schaub - litb