我们的问题是,在C语言中i++和++i的性能有区别吗?
c++的答案是什么?
我们的问题是,在C语言中i++和++i的性能有区别吗?
c++的答案是什么?
当前回答
说编译器不能优化掉后缀情况下的临时变量副本是不完全正确的。用VC进行的快速测试表明,至少在某些情况下,它可以做到这一点。
在下面的例子中,生成的代码对于前缀和后缀是相同的,例如:
#include <stdio.h>
class Foo
{
public:
Foo() { myData=0; }
Foo(const Foo &rhs) { myData=rhs.myData; }
const Foo& operator++()
{
this->myData++;
return *this;
}
const Foo operator++(int)
{
Foo tmp(*this);
this->myData++;
return tmp;
}
int GetData() { return myData; }
private:
int myData;
};
int main(int argc, char* argv[])
{
Foo testFoo;
int count;
printf("Enter loop count: ");
scanf("%d", &count);
for(int i=0; i<count; i++)
{
testFoo++;
}
printf("Value: %d\n", testFoo.GetData());
}
无论您使用的是++testFoo还是testfoo++,都将得到相同的结果代码。事实上,无需从用户读取计数,优化器将整个事情归结为一个常数。所以这个:
for(int i=0; i<10; i++)
{
testFoo++;
}
printf("Value: %d\n", testFoo.GetData());
结果如下:
00401000 push 0Ah
00401002 push offset string "Value: %d\n" (402104h)
00401007 call dword ptr [__imp__printf (4020A0h)]
因此,虽然后缀版本肯定会更慢,但如果你不使用它,优化器可能会足够好,可以摆脱临时副本。
其他回答
有意的问题是关于什么时候结果是未使用的(这从C的问题中很明显)。有人能解决这个问题吗,因为这个问题是“社区维基”?
关于过早优化,Knuth经常被引用。这是正确的。但是Donald Knuth永远不会用你现在看到的那些可怕的代码来辩护。见过Java整数(不是int)中的a = b + c吗?这相当于3次装箱/开箱转换。避免这样的事情很重要。无用地写i++而不是++i也是同样的错误。 编辑:正如phresnel在评论中所言,这可以总结为“过早的优化是邪恶的,过早的悲观也是”。
甚至人们更习惯于i++这一事实也是一个不幸的C遗产,是由K&R的一个概念错误造成的(如果你遵循意图论点,这是一个合乎逻辑的结论;为K&R辩护因为他们是K&R是毫无意义的,他们很伟大,但作为语言设计师他们并不伟大;C设计中存在无数错误,从gets()到strcpy(),再到strncpy() API(它应该从第一天开始就有strlcpy() API)。
顺便说一句,我是那些不太习惯c++的人之一,觉得c++ I读起来很烦人。尽管如此,我仍然使用它,因为我承认它是正确的。
++i比i++快,因为它不返回值的旧副本。
它也更直观:
x = i++; // x contains the old value of i
y = ++i; // y contains the new value of i
这个C语言的例子输出的是“02”而不是你所期望的“12”:
#include <stdio.h>
int main(){
int a = 0;
printf("%d", a++);
printf("%d", ++a);
return 0;
}
c++也是一样:
#include <iostream>
using namespace std;
int main(){
int a = 0;
cout << a++;
cout << ++a;
return 0;
}
Mark:只是想指出操作符++是很好的内联候选者,如果编译器选择这样做,在大多数情况下多余的拷贝将被消除。(例如POD类型,迭代器通常是这种类型。)
也就是说,在大多数情况下使用++iter仍然是更好的风格。: -)
说编译器不能优化掉后缀情况下的临时变量副本是不完全正确的。用VC进行的快速测试表明,至少在某些情况下,它可以做到这一点。
在下面的例子中,生成的代码对于前缀和后缀是相同的,例如:
#include <stdio.h>
class Foo
{
public:
Foo() { myData=0; }
Foo(const Foo &rhs) { myData=rhs.myData; }
const Foo& operator++()
{
this->myData++;
return *this;
}
const Foo operator++(int)
{
Foo tmp(*this);
this->myData++;
return tmp;
}
int GetData() { return myData; }
private:
int myData;
};
int main(int argc, char* argv[])
{
Foo testFoo;
int count;
printf("Enter loop count: ");
scanf("%d", &count);
for(int i=0; i<count; i++)
{
testFoo++;
}
printf("Value: %d\n", testFoo.GetData());
}
无论您使用的是++testFoo还是testfoo++,都将得到相同的结果代码。事实上,无需从用户读取计数,优化器将整个事情归结为一个常数。所以这个:
for(int i=0; i<10; i++)
{
testFoo++;
}
printf("Value: %d\n", testFoo.GetData());
结果如下:
00401000 push 0Ah
00401002 push offset string "Value: %d\n" (402104h)
00401007 call dword ptr [__imp__printf (4020A0h)]
因此,虽然后缀版本肯定会更慢,但如果你不使用它,优化器可能会足够好,可以摆脱临时副本。
即使在没有性能优势的内置类型上也应该使用++i的原因是为了给自己养成一个好习惯。