通过指针访问内存据说比通过数组访问内存更有效.我正在学习C语言,以上内容在K&R.他们特别说

任何可以通过数组下标实现的操作也可以通过指针完成.指针版本通常会更快

我用Visual C++组装了以下代码.(我的是686处理器.我已禁用所有优化.)

int a[10], *p = a, temp;

void foo()
{
    temp = a[0];
    temp = *p;
}

令我惊讶的是,我发现通过指针进行的内存访问将3条指令与通过数组进行的内存访问的两条指令相比较.下面是相应的代码.

; 5    : temp = a[0];

    mov eax, DWORD PTR _a
    mov DWORD PTR _temp, eax

; 6    : temp = *p;

    mov eax, DWORD PTR _p
    mov ecx, DWORD PTR [eax]
    mov DWORD PTR _temp, ecx

请帮我理解一下.我在这里错过了什么??


正如许多答案和 comments 所指出的,我使用了一个编译时常数作为数组索引,从而使通过数组进行访问变得更容易.下面是以变量作为索引的汇编代码.现在,通过指针和数组访问的指令数量相等.我提出的更广泛的问题仍然适用.通过指针访问内存并不意味着效率更高.

; 7    :        temp = a[i];

    mov eax, DWORD PTR _i
    mov ecx, DWORD PTR _a[eax*4]
    mov DWORD PTR _temp, ecx

; 8    : 
; 9    :    
; 10   :        temp = *p;

    mov eax, DWORD PTR _p
    mov ecx, DWORD PTR [eax]
    mov DWORD PTR _temp, ecx

推荐答案

据说通过指针进行的存储器访问比通过数组进行的存储器访问效率更高.

在过go ,当编译器是相对愚蠢的野兽时,这可能是真的.您只需要在高优化模式下查看gcc的部分代码输出,就知道它不再正确.其中一些代码很难理解,但一旦你理解了,它的光辉是显而易见的.

一个好的编译器将为指针访问和数组访问生成相同的代码,您可能不应该担心这种性能级别.编写编译器的人对目标体系 struct 的了解远远超过我们这些凡人.在优化代码(算法 Select 等)时,更多地关注宏观层面,并信任工具制造商完成他们的工作.


事实上,我很惊讶编译器没有优化整个

temp = a[0];

行不存在,因为temp在下一行用不同的值重写,而a决不标记为volatile.

我记得很久以前的一个城市神话,关于最新的VAX Fortran编译器(在这里显示我的年龄)的基准,它的性能比竞争对手好几个数量级.

事实证明,编译器发现基准计算的结果没有在任何地方使用,所以它优化了整个计算循环,使其被遗忘.因此,运行速度有了实质性的提高.


Update:在特定情况下,优化代码更有效的原因在于您找到位置的方式.a将位于链接/加载时确定的固定位置,同时对其进行参考.所以a[0]a[any constant]将位于一个固定的位置.

出于同样的原因,p本身也将位于固定位置.But *p(p的内容)是可变的,因此需要进行额外的查找以找到正确的内存位置.

您可能会发现,将另一个变量x设置为0(而不是const)并使用a[x]也会引入额外的计算.


在你的一条 comments 中,你说:

按照您的建议进行操作也会产生3条通过数组进行内存访问的指令(读取索引、提取数组元素的值、存储在TEMP中).但是我还是看不到效率.:-(

我的回答是,你很可能会看到使用指针的效率.现代编译器的任务不仅仅是找出数组操作和指针操作可以转换为相同的底层机器代码.

事实上,如果不启用优化,指针代码的效率可以达到less%.考虑下面的翻译:

int *pa, i, a[10];

for (i = 0; i < 10; i++)
    a[i] = 100;
/*
    movl    $0, -16(%ebp)              ; this is i, init to 0
L2:
    cmpl    $9, -16(%ebp)              ; from 0 to 9
    jg      L3
    movl    -16(%ebp), %eax            ; load i into register
    movl    $100, -72(%ebp,%eax,4)     ; store 100 based on array/i
    leal    -16(%ebp), %eax            ; get address of i
    incl    (%eax)                     ; increment
    jmp     L2                         ; and loop
L3:
*/

for (pa = a; pa < a + 10; pa++)
    *pa = 100;
/*
    leal    -72(%ebp), %eax
    movl    %eax, -12(%ebp)            ; this is pa, init to &a[0]
L5:
    leal    -72(%ebp), %eax
    addl    $40, %eax
    cmpl    -12(%ebp), %eax            ; is pa at &(a[10])
    jbe     L6                         ; yes, stop
    movl    -12(%ebp), %eax            ; get pa
    movl    $100, (%eax)               ; store 100
    leal    -12(%ebp), %eax            ; get pa
    addl    $4, (%eax)                 ; add 4 (sizeof int)
    jmp     L5                         ; loop around
L6:
*/

从该示例中,您实际上可以看到指针示例更长,为unnecessarily so.它在不变的情况下多次将pa加载到%eax中,实际上在pa&(a[10])之间交替使用%eax.这里的默认优化基本上是根本不优化.

当您切换到优化级别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的实际运行速度,除非您执行billions个这样的操作,否则您不会注意到差别.我总是倾向于为了可读性而编写代码,只有在它成为问题时才会担心性能.

顺便说一句,你引用的那句话:

5.3指针和数组:指针版本通常会更快,但至少对新手来说,更难立即掌握.

这可以追溯到K&;R的最早版本,包括我1978年的旧版本,其中函数仍然是写的:

getint(pn)
int *pn;
{
    ...
}

从那时起,编译器走过了漫长的道路.

C++相关问答推荐

海湾合作委员会是否保证大小匹配的访问?

C sscanf没有捕获第二个参数

如何启用ss(另一个调查套接字的实用程序)来查看Linux主机上加入的多播组IP地址?

如何在C中只使用一个带双方括号([i][j])访问语法的malloc来分配动态大小的2d数组?

在struct中调用函数,但struct在void中 *

如何一次获取一个字符

当execvp在C函数中失败时杀死子进程

核心转储文件中出现奇怪的大小变化

在传统操作系统上可以在虚拟0x0写入吗?

模拟shell并运行.sh文件

为什么sscanf不能正确地从这个字符串格式中提取所有数字?

1处的解析器错误:yacc语法的语法错误

如何使解释器存储变量

如何在C++中安全地进行浮点运算

将变量或参数打包到 struct /联合中是否会带来意想不到的性能损失?

用C++构建和使用DLL的困惑

从整型转换为浮点型可能会改变其值.

将char*数组深度复制到 struct 中?

`%%的sscanf无法按预期工作

是什么阻止编译器优化手写的 memcmp()?