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