在C语言中,a += b比a = a + b更有效吗?

12

我知道在某些语言中以下内容:

a += b

比起以下方式更加高效:

a = a + b

因为它省去了创建临时变量的需求。这在C语言中是否也是这样?使用+=(因此也包括-=*=等)更高效吗?


17
相较于现代编译器为了优化而能够进行的操作,将一个东西转变成另一个东西是微不足道的。 - Pascal Cuoq
1
@Brian:C语言没有引用类型。 - Johan Kotlinski
1
哦,哇,我完全误读了标签... - Brian Driscoll
2
@robintw 这听起来像是IDL的缺陷。每当两个语义等效的指令具有不同的性能特征时,这必须归因于语言/解释器/编译器等的弱点。 - David Heffernan
3
我有一个傻瓜问题:为什么不去测试一下呢?我的意思是,编写一个循环来运行一百万次并计时,这有多难呢?最好的部分是,你将得到一个明确的答案,而不是使用不同编译器、运行在不同操作系统和处理器上的人的意见。 - JUST MY correct OPINION
显示剩余6条评论
7个回答

37

这里给出一个明确的答案...

$ cat junk1.c
#include <stdio.h>

int main()
{
    long a, s = 0;
    for (a = 0; a < 1000000000; a++)
    {
        s = s + a * a;
    }
    printf("Final sum: %ld\n", s);
}

michael@isolde:~/junk$ cat junk2.c
#include <stdio.h>

int main()
{
    long a, s = 0;
    for (a = 0; a < 1000000000; a++)
    {
        s += a * a;
    }
    printf("Final sum: %ld\n", s);
}

michael@isolde:~/junk$ for a in *.c ; do gcc -O3 -o ${a%.c} $a ; done
michael@isolde:~/junk$ time ./junk1
Final sum: 3338615082255021824

real    0m2.188s
user    0m2.120s
sys 0m0.000s
michael@isolde:~/junk$ time ./junk2
Final sum: 3338615082255021824

real    0m2.179s
user    0m2.120s
sys 0m0.000s

这是针对我的电脑、我的编译器和我的操作系统的测试结果。您的结果可能会有所不同。然而,在我的系统上,时间相同:用户时间为2.120秒。

现在,为了展示现代编译器的强大之处,您会注意到我在赋值中使用了表达式a * a。这是因为存在这个小问题:

$ cat junk.c
#include <stdio.h>

int main()
{
    long a, s = 0;
    for (a = 0; a < 1000000000; a++)
    {
        s = s + a;
    }
    printf("Final sum: %ld\n", s);
}

michael@isolde:~/junk$ gcc -O3 -S junk.c
michael@isolde:~/junk$ cat junk.s 
    .file   "junk.c"
    .section    .rodata.str1.1,"aMS",@progbits,1
.LC0:
    .string "Final sum: %ld\n"
    .text
    .p2align 4,,15
.globl main
    .type   main, @function
main:
.LFB22:
    .cfi_startproc
    movabsq $499999999500000000, %rdx
    movl    $.LC0, %esi
    movl    $1, %edi
    xorl    %eax, %eax
    jmp __printf_chk
    .cfi_endproc
.LFE22:
    .size   main, .-main
    .ident  "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
    .section    .note.GNU-stack,"",@progbits
编译器发现我的循环并展开它,以计算累加和,并将其作为常量嵌入并直接打印出来,完全跳过任何循环构造。在面对如此聪明的优化器时,你真的认为你能够找到任何有意义的差异来区分 s = s + a s += a?!

22

这实际上是一个编译器特定的问题,但我希望所有现代编译器都会给出相同的结果。使用Visual Studio 2008:

int main() {  
    int a = 10;  
    int b = 30;  
    a = a + b;  
    int c = 10;  
    int d = 50;  
    c += d;  
}  

代码行 a = a + b 的反汇编结果如下:

0014139C  mov         eax,dword ptr [a]   
0014139F  add         eax,dword ptr [b]   
001413A2  mov         dword ptr [a],eax   

代码行 c += d 的反汇编结果如下:

001413B3  mov         eax,dword ptr [c]   
001413B6  add         eax,dword ptr [d]   
001413B9  mov         dword ptr [c],eax   

这是一样的。它们编译成相同的代码。


17

这取决于 a 是什么。

C 语言中的 a += b 从本质上讲等同于 a = a + b,只是在抽象的角度中,前一种变体中只对 a 求值一次。如果 a 是一个“纯”值,即对程序行为没有影响,那么无论是效率还是其他方面,a += ba = a + b 在所有方面严格等价。

换句话说,在实际可以自由选择 a += ba = a + b(也就是它们做相同事情的情况下),它们通常具有完全相同的效率。某些编译器可能会在 a 代表函数调用时(例如)出现困难,但当 a 是一个非易失性变量时,两个表达式生成的机器代码将是相同的。

以另一个例子,如果 a 是一个易失性变量,则 a += ba = a + b 具有不同的行为和效率。然而,由于它们不是等价的,因此您的问题在这种情况下根本不适用。


6
在问题中展示的简单情况下,没有显著差异。 当您有像这样的表达式时,赋值运算符得分:
s[i]->m[j1].k = s[i]->m[jl].k + 23;   // Was that a typo?

vs:

s[i]->m[j1].k += 23;

两个好处——我不算少打字。第一个和第二个表达式不同时,就不会有错别字的问题;编译器也不会对复杂表达式进行两次评估。这些日子来说,优化编译器比以前要好得多,所以这可能不会有太大的区别,但是你可能会有更复杂的表达式(例如,在下标中作为另一个翻译单元中定义的函数的一部分进行计算),在这种情况下编译器可能无法避免两次评估表达式:

s[i]->m[somefunc(j1)].k = s[i]->m[somefunc(j1)].k + 23;

s[i]->m[somefunc(j1)].k += 23;

此外,如果你足够勇敢,你可以写下以下内容:
s[i++]->m[j1++].k += 23;

但你不能这样写:

s[i++]->m[j1++].k = s[i]->m[j1].k + 23;
s[i]->m[j1].k = s[i++]->m[j1++].k + 23;

因为计算顺序未定义,所以可以使用任何其他排列方式。

1
我希望我可以给予+10分,因为你指出了一种情况,即使用+=可以使代码更易读和可靠,而没有提及“击键少”这个说法。(更不用说提到那些在语义上+==...+之间有差异的情况了。) - JUST MY correct OPINION

3
a += b

比起更有效率
a = a + b

由于前者只需6个按键,而后者需要9个按键,因此前者更高效。随着现代硬件的发展,即使编译器对其中一个使用较慢的代码,整个程序的寿命中节省的总时间可能仍然少于您输入额外三个按键所需的时间。然而,正如其他人所说,编译器几乎肯定会生成完全相同的代码,因此前者更有效率。即使考虑到可读性,大多数C程序员可能会更快地解析前者,因为这是一种常见的模式。

1
如果你用按键次数来衡量效率,那么你的编程水平非常糟糕。 - JUST MY correct OPINION
@仅代表我的正确观点:编程时,你是在解决问题。编写程序所花费的时间是产生解决方案的总成本的一部分,就像运行完成的程序所花费的时间一样。不要错误地认为你自己(作为程序员)的时间没有价值。 - JeremyP
如果三个按键对于你解决问题有如此大的影响,那么你的编程水平非常糟糕。 - JUST MY correct OPINION
2
@仅是我的正确观点:你所说的“那么大的差别”是什么意思?我在哪里量化了节省?按顺序按九个键比按六个键需要更长时间,这又怎么能证明你的编程非常糟糕呢? - JeremyP

2
在几乎所有情况下,两者产生的结果相同。

现在我很好奇,你能举出一个它们不相同的情况吗? - Brad Christie
@Brad 我记得读过一篇文章,说更紧凑的C结构通常会产生更紧凑的代码。我无法指出具体案例,但我想到了早期的C编译器。我看不出现代编译器会产生不同的结果。 - Jonathan Wood
好的,你刚才用“virtually”这个词引起了我的好奇心。感谢你的回答。 - Brad Christie

2

除非使用真正古老或写得很差的编译器,否则没有区别,只要ab是普通变量,这两个变量会产生相同的结果。

如果你在处理C ++而不是C,则运算符重载将允许有更实质性的差异。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接