Answers:
内容提要:不。
i++
可能会比慢++i
,因为i
可能需要保存的旧值供以后使用,但实际上所有现代编译器都会对此进行优化。
我们可以通过查看这个函数的代码演示了这一点,都与++i
和i++
。
$ cat i++.c
extern void g(int i);
void f()
{
int i;
for (i = 0; i < 100; i++)
g(i);
}
这些文件是相同的,除了++i
和i++
:
$ diff i++.c ++i.c
6c6
< for (i = 0; i < 100; i++)
---
> for (i = 0; i < 100; ++i)
我们将对其进行编译,并获得生成的汇编器:
$ gcc -c i++.c ++i.c
$ gcc -S i++.c ++i.c
我们可以看到生成的对象文件和汇编文件都是相同的。
$ md5 i++.s ++i.s
MD5 (i++.s) = 90f620dda862cd0205cd5db1f2c8c06e
MD5 (++i.s) = 90f620dda862cd0205cd5db1f2c8c06e
$ md5 *.o
MD5 (++i.o) = dd3ef1408d3a9e4287facccec53f7d22
MD5 (i++.o) = dd3ef1408d3a9e4287facccec53f7d22
++i
而不是仍然是一种好习惯i++
。绝对没有理由不这样做,而且如果您的软件通过了无法对其进行优化的工具链,那么您的软件将更加高效。考虑到键入++i
和键入一样容易i++
,所以实际上没有任何理由不使用++i
它。
从效率与意图(作者Andrew Koenig):
首先,至少在涉及整数变量的情况下,
++i
要比效率更高还远不是显而易见的i++
。
和:
因此,一个人应该问的问题不是这两个操作中的哪个更快,而是这两个操作中的哪个更准确地表达了您要完成的任务。我认为,如果您不使用表达式的值,那么就没有理由使用
i++
而不是++i
,因为没有理由要复制变量的值,增加变量然后扔掉副本。
因此,如果不使用结果值,我将使用++i
。但这不是因为它更有效:因为它正确地表达了我的意图。
i++
以同样的方式我最好的代码i += n
或者i = i + n
,即在形式目标 动词 对象,与目标操作数左边的动词运营商。在情况下i++
,没有右对象,但是规则仍然适用,将目标保持在动词运算符的左侧。
更好的答案是++i
有时会更快,但永远不会慢。
每个人似乎都假设这i
是一个常规的内置类型,例如int
。在这种情况下,没有可测量的差异。
但是,如果i
是复杂类型,则可能会发现可测量的差异。因为i++
您必须在增加课程之前制作课程的副本。实际上,根据副本所涉及的内容,它可能会变慢,因为++it
您只需返回最终值即可。
Foo Foo::operator++()
{
Foo oldFoo = *this; // copy existing value - could be slow
// yadda yadda, do increment
return oldFoo;
}
另一个区别是++i
您可以选择返回引用而不是值。同样,根据制作对象副本所涉及的内容,这可能会更慢。
迭代器的使用是一个现实的例子。复制迭代器在您的应用程序中不太可能成为瓶颈,但是,仍然要养成使用++i
而不是i++
不影响结果的习惯,这是一个好习惯。
简短答案:
从未有任何区别i++
,并++i
在速度方面。好的编译器在两种情况下不应生成不同的代码。
长答案:
每隔什么其他答案没有提到的是,之间的区别++i
与i++
只能使发现的表达中的意义。
在的情况下for(i=0; i<n; i++)
,i++
本身在其自己的表达式中:在之前有一个序列点,在其i++
之后有一个点。因此,唯一生成的机器代码是“ increment i
by 1
”,并且很好地定义了如何相对于程序的其余部分对其进行排序。因此,即使将其更改为prefix ++
,也没有关系,您仍然只会获得机器代码“ increment i
by 1
”。
++i
和之间的差异i++
仅在诸如array[i++] = x;
vs之类的表达式中起作用array[++i] = x;
。有人可能会说,后缀在这种操作中会变慢,因为i
必须稍后重新加载驻留的寄存器。但是请注意,只要编译器不按C标准所称的那样“破坏抽象机的行为”,它就可以按照自己喜欢的任何方式随意指令。
因此,尽管您可能会假设array[i++] = x;
将其转换为机器代码,但是:
i
在寄存器A中。i
将//的值存储在寄存器A //中效率低下,因为这里有额外的指令,我们已经执行了一次。i
。编译器也可能更有效地产生代码,例如:
i
在寄存器A中。i
。仅仅因为您作为C程序员已被训练为认为后缀出现++
在末尾,所以不必以这种方式对机器代码进行排序。
因此,++
在C语言中,前缀和后缀之间没有区别。作为C程序员,您现在应该有所不同的是,在某些情况下不一致使用前缀的人和在其他情况下不一致使用后缀的人,没有任何理由。这表明他们不确定C的工作方式或对语言的了解不正确。这总是一个不好的信号,它确实表明他们正在基于迷信或“宗教教条”在程序中做出其他可疑的决定。
++
实际上,“前缀总是更快”确实是这样一种错误的教条,在准C程序员中很常见。
摘自Scott Meyers的《更有效的c ++ 项目6》:区分增量和减量运算的前缀和后缀形式。
在对象方面,尤其是在迭代器方面,始终优先使用前缀版本而不是后缀。
如果查看运营商的呼叫方式,则其原因是。
// Prefix
Integer& Integer::operator++()
{
*this += 1;
return *this;
}
// Postfix
const Integer Integer::operator++(int)
{
Integer oldValue = *this;
++(*this);
return oldValue;
}
看这个例子,很容易看出前缀运算符将总是比后缀更有效率。由于在使用postfix时需要一个临时对象。
这就是为什么当您看到使用迭代器的示例时,它们始终使用前缀版本的原因。
但是正如您为int指出的那样,由于可以进行编译器优化,因此实际上没有区别。
如果您担心微优化,这是另外一个观察结果。给定以下条件,递增循环“可能”比递增循环更有效(取决于指令集架构,例如ARM):
for (i = 0; i < 100; i++)
在每个循环中,您将分别具有一条指令:
1
到中i
。 i
小于100
。i
小于100
。而递减循环:
for (i = 100; i != 0; i--)
该循环将为每个指令提供一条指令:
i
,设置CPU寄存器状态标志。Z==0
)的条件分支。当然,这仅在减为零时有效!
从《 ARM系统开发人员指南》中记住。
请不要让“哪个更快”的问题成为使用哪个的决定因素。您可能永远不需要那么在意,此外,程序员的阅读时间比机器学习的时间要昂贵得多。
使用对人类阅读代码最有意义的方法。
首先:i++
和之间的区别++i
在C中可以忽略不计。
到细节。
++i
速度更快在C ++中,++i
如果i
某种对象带有重载的增量运算符,则效率更高。
为什么?
在中++i
,对象首先递增,然后可以作为const引用传递给任何其他函数。如果表达式是foo(i++)
因为这是不可能的,因为现在增量需要在foo()
调用之前完成,但是旧值需要传递给foo()
。因此,编译器i
在对原始文件执行增量运算符之前,必须对其进行复制。额外的构造函数/析构函数调用是最糟糕的部分。
如上所述,这不适用于基本类型。
i++
可能更快如果不需要调用构造函数/析构函数,这在C语言中总是如此,++i
并且i++
应该同样快,对吗?不。它们的速度几乎一样快,但可能会有细微的差异,大多数其他应答者都绕错了方向。
怎么i++
会更快?
关键是数据依赖性。如果需要从内存中加载该值,则需要对其进行两个后续操作,使其递增并使用它。使用时++i
,需要先进行递增,然后才能使用该值。使用i++
,使用不取决于增量,并且CPU可以与增量操作并行执行使用操作。区别最多是一个CPU周期,因此它确实可以忽略不计,但是确实存在。这是许多人期望的另一种方式。
++i
或,i++
则在它们之间进行更改会更改表达式的语义,因此毫无疑问会带来任何性能提升/损失。如果它们是独立的,即该操作的结果没有立即使用,则任何合适的编译器都可以将其编译为相同的东西,例如INC
汇编指令。
i++
并++i
可以互换在几乎每一个可能的情况下,通过一个调整环常量使用,因此他们在他们的程序员做什么接近相等。2)即使两者都编译为同一条指令,但对于CPU它们的执行也有所不同。在的情况下i++
,CPU可以与其他使用相同值的指令并行计算增量(CPU确实会执行此操作!),而++i
CPU必须在增量之后安排其他指令。
if(++foo == 7) bar();
和if(foo++ == 6) bar();
在功能上等效。但是,第二个可能快一个周期,因为比较和增量可以由CPU并行计算。并不是说这单个周期有多大关系,而是有区别。
<
例如vs <=
)++
,因此,通常之间很容易实现转换。
@Mark尽管允许编译器优化变量的(基于堆栈的)临时副本,而gcc(在最新版本中)正在这样做,但这并不意味着所有编译器都会一直这样做。
我只是用我们当前项目中使用的编译器对其进行了测试,而四分之三的代码并未对其进行优化。
永远不要假设编译器正确无误,尤其是在可能更快但永远不会慢的代码易于阅读的情况下。
如果您的代码中没有真正愚蠢的操作符实现:
比起i ++,Alwas更喜欢++ i。
我可以想到后缀比前缀增量慢的情况:
想象一下,带有寄存器的处理器A
被用作累加器,并且它是许多指令中使用的唯一寄存器(某些小型微控制器实际上就是这样)。
现在,假设以下程序及其转换为假设的程序集:
前缀增量:
a = ++b + c;
; increment b
LD A, [&b]
INC A
ST A, [&b]
; add with c
ADD A, [&c]
; store in a
ST A, [&a]
后缀增量:
a = b++ + c;
; load b
LD A, [&b]
; add with c
ADD A, [&c]
; store in a
ST A, [&a]
; increment b
LD A, [&b]
INC A
ST A, [&b]
请注意如何b
强制重新加载的值。使用前缀递增,编译器可以仅递增该值并继续使用它,可能避免重新加载它,因为所需的值在递增之后已经在寄存器中。但是,使用后缀增量,编译器必须处理两个值,一个是旧值,另一个是增量值,正如我在上面显示的那样,这将导致更多的内存访问。
当然,如果不使用增量值(例如单个i++;
语句),则编译器可以(并且确实)简单地生成增量指令,而不考虑后缀或前缀的用法。
附带说明一下,我想提到一个表达式,其中存在一个a的表达式b++
不能简单地转换为一个表达式++b
而无需付出额外的努力(例如,通过添加- 1
)。因此,如果将两者作为某个表达式的一部分,则将它们进行比较是不正确的。通常,在b++
表达式内部使用的地方就不能使用++b
,因此即使++b
效率更高,也完全是错误的。如果表达式请求它,当然是例外(例如a = b++ + 1;
,可以将其更改为a = ++b;
)。
我一直在读通过这里的大部分答案,许多的意见,我没有看到任何参考一个例子,我能想到的地方i++
是不是更有效++i
(也许令人惊讶的--i
是不是更有效i--
)。那是针对DEC PDP-11的C编译器!
PDP-11具有汇编指令,用于寄存器的递减和递增,但不能相反。该指令允许将任何“通用”寄存器用作堆栈指针。因此,如果您使用类似的东西*(i++)
,可以将其编译为单个汇编指令,而*(++i)
不能这样做。
这显然是一个非常深奥的例子,但确实提供了例外,即后增量效率更高(或者,我应该说是,因为这几天对PDP-11 C代码的需求不大)。
--i
和的PDP-11程序员之一i++
。
我总是喜欢预增量,但是...
我想指出的是,即使在调用operator ++函数的情况下,如果函数被内联,编译器也将能够优化掉临时函数。由于operator ++通常很短,并且经常在标头中实现,因此很可能会内联。
因此,出于实际目的,这两种形式的性能可能没有太大区别。但是,我总是更喜欢预增量,因为直接表达我想说的话似乎要好一些,而不要依靠优化器来解决。
同样,减少优化器的工作量可能意味着编译器运行得更快。
我的C有点生锈,所以我提前道歉。从速度上看,我可以理解结果。但是,我对这两个文件如何出现在相同的MD5哈希中感到困惑。也许for循环运行相同,但是以下两行代码不会生成不同的汇编吗?
myArray[i++] = "hello";
与
myArray[++i] = "hello";
第一个将值写入数组,然后递增i。然后,第二个增量i写入数组。我不是汇编专家,但我只是看不到这两行不同的代码将如何生成相同的可执行文件。
只是我的两分钱。
foo[i++]
到foo[++i]
没有其他任何更改显然会改变程序的语义,但是在某些处理器上使用编译器没有环冲顶优化逻辑时,递增p
和q
一次,然后运行一个循环,其进行例如*(p++)=*(q++);
会比使用一个循环执行得更快*(++pp)=*(++q);
。对于某些处理器上非常紧密的循环,速度差异可能会很大(超过10%),但这可能是C语言中唯一的情况,后增量比预增量快得多。