在C中,+ = b比a = a + b更有效吗?
我在某些语言中知道以下内容:
a += b
比以下更有效:
a = a + b
因为它不需要创建临时变量。 这是C的情况吗? 使用+ =更高效(因此也-=
*=
等)
所以这是一个明确的答案……
$ cat junk1.c #include 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 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 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
?时找到任何有意义的优势!
这确实是一个编译器特定的问题,但我希望所有现代编译器都会给出相同的结果。 使用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
哪个是一样的。 它们被编译成相同的代码。
这取决于什么是。
根据定义,C中的a += b
等于a = a + b
,除了从抽象的观点来看a
在前一种变体中仅被评估一次。 如果a
是一个“纯”值,即如果对它进行一次评估而不是多次评估它对程序行为没有影响,则a += b
a = a + b
在所有方面都严格等同于a = a + b
,包括效率。
换句话说,在实际上在a += b
和a = a + b
之间有自由选择的情况下(意味着你知道它们做同样的事情),它们通常具有完全相同的效率。 当a
代表函数调用时(某些示例;可能不是您的意思),某些编译器可能会遇到困难,但当a
是非易失性变量时,为两个表达式生成的机器代码将是相同的。
再例如,如果a
是易变量,则a += b
和a = a + b
具有不同的行为,因此效率不同。 但是,由于它们不相同,因此您的问题根本不适用于此类情况。
在问题中显示的简单案例中,没有显着差异。 赋值运算符分数是指具有以下表达式的表达式:
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;
(或任何其他排列)因为评估顺序没有定义。
a += b
效率比
a = a + b
因为前者带你6次击键而后者带你9次击键。
使用现代硬件,即使编译器是愚蠢的并且使用较慢的代码而不是另一个,在程序的生命周期中节省的总时间可能小于键入三个额外键击所花费的时间。
但是,正如其他人所说,编译器几乎肯定产生完全相同的代码,因此前者更有效。
即使您考虑到可读性,大多数C程序员可能比后者更快地在心理上解析前者,因为它是如此常见的模式。
几乎在所有情况下,两者产生相同的结果。
除了真正古老或无法编写的编译器之外,应该没有区别,只要a
和b
是正常变量,因此两者产生相同的结果。
如果您正在处理C ++而不是C,那么运算符重载将允许存在更多实质性差异。