11

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

a += b

比:

a = a + b

因为它消除了创建临时变量的需要。这是C中的情况吗?使用 += 是否更有效(因此也是-= *=等)

4

7 回答 7

36

所以这里有一个确定的答案...

$ 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吗?!

于 2011-02-17T17:08:05.463 回答
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   

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

于 2011-02-17T17:14:56.380 回答
17

这取决于是什么a

a += b在 C 中,根据定义等同于a = a + b,除了从抽象的角度来看a,在前一个变体中只计算一次。如果a是一个“纯”值,即如果评估一次与多次评估对程序行为没有影响,那么在所有方面a += b都严格等同a = a + b于,包括效率。

换句话说,在您实际上可以在a += b和之间自由选择的情况下a = a + b(意味着您知道它们做同样的事情),它们通常具有完全相同的效率。一些编译器在a代表函数调用时可能会遇到困难(例如;可能不是您的意思),但是当a是非易失性变量时,为两个表达式生成的机器代码将是相同的。

再举一个例子,ifa是一个 volatile 变量,则a += ba = a + b具有不同的行为,因此具有不同的效率。但是,由于它们不等效,因此您的问题根本不适用于这种情况。

于 2011-02-17T16:25:22.573 回答
6

在问题中显示的简单案例中,没有显着差异。赋值运算符得分的地方是当你有一个表达式时,例如:

s[i]->m[j1].k = s[i]->m[jl].k + 23;   // Was that a typo?

与:

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;

(或任何其他排列),因为未定义评估顺序。

于 2011-02-19T05:03:19.480 回答
3
a += b

a = a + b

因为前者需要 6 次击键,而后者需要 9 次击键。

使用现代硬件,即使编译器很愚蠢并且使用比另一个慢的代码,在程序的整个生命周期中节省的总时间也可能少于您键入三个额外击键所花费的时间。

但是,正如其他人所说,编译器几乎肯定会产生完全相同的代码,因此前者效率更高。

即使考虑到可读性,大多数 C 程序员在心理上可能比后者更快地解析前者,因为它是如此常见的模式。

于 2011-02-17T17:42:36.667 回答
2

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

于 2011-02-17T16:23:50.213 回答
2

除了使用真正古老或编写不称职的编译器之外,应该没有区别,只要ab是正常变量,因此两者会产生相同的结果。

如果您使用的是 C++ 而不是 C,则运算符重载将允许存在更多实质性差异。

于 2011-02-17T16:48:29.447 回答