在C中,i ++和++ i之间有性能差异吗?

如果不使用结果值, i++++i之间是否存在性能差异?

执行摘要:不

i++可能会慢于++i ,因为++i的旧值可能需要保存以供日后使用,但实际上所有现代编译器将优化这一点。

我们可以通过查看这个函数的代码,用++ii++来证明这一点。

 $ cat i++.c extern void g(int i); void f() { int i; for (i = 0; i < 100; i++) g(i); } 

这些文件是相同的,除了++ii++

 $ diff i++.c ++ic 6c6 < for (i = 0; i < 100; i++) --- > for (i = 0; i < 100; ++i) 

我们将编译它们,并获取生成的汇编器:

 $ gcc -c i++.c ++ic $ gcc -S i++.c ++ic 

而且我们可以看到生成的对象和汇编文件都是一样的。

 $ md5 i++.s ++is MD5 (i++.s) = 90f620dda862cd0205cd5db1f2c8c06e MD5 (++is) = 90f620dda862cd0205cd5db1f2c8c06e $ md5 *.o MD5 (++io) = dd3ef1408d3a9e4287facccec53f7d22 MD5 (i++.o) = dd3ef1408d3a9e4287facccec53f7d22 

从效率与意图通过安德鲁柯尼希:

首先, ++ii++更有效率,至少在整数变量的情况下,这是很不明显的。

和:

所以应该问的问题不是这两个操作中的哪一个更快,而是这两个操作中的哪一个更准确地表达你正在尝试完成的操作。 我提出,如果你没有使用表达式的值,从来没有理由使用i++而不是++i ,因为从来没有理由复制变量的值,增加变量,然后抛出复制。

所以,如果不使用结果值,我会使用++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++ ,那么结果不会受到影响。

如果你担心微观优化,这里有一个额外的观察。 递减循环可能会比增加循环更有效(取决于指令集架构,例如ARM),给出:

 for (i = 0; i < 100; i++) 

在每个循环中,你将有一个指令:

  1. i 1
  2. 比较i是不是一个100
  3. 一个条件分支,如果i小于100

而递减循环:

 for (i = 100; i != 0; i--) 

循环将有一个指令为每个:

  1. 减少i ,设置CPU寄存器状态标志。
  2. 一个条件分支,取决于CPU寄存器状态( Z==0 )。

当然这只有在递减到零时才起作用!

从“ARM系统开发人员指南”中记得。

从斯科特迈耶斯, 更有效的c + + 项目6:区分前缀和后缀形式的增量和减量操作

对于对象,前缀版本总是优于后缀,特别是对于迭代器。

如果你看看运营商的呼叫模式,这个原因。

 // Prefix Integer& Integer::operator++() { *this += 1; return *this; } // Postfix const Integer Integer::operator++(int) { Integer oldValue = *this; ++(*this); return oldValue; } 

看看这个例子,很容易看到前缀运算符总是比后缀更有效率。 因为在使用后缀的时候需要一个临时对象。

这就是为什么当你看到使用迭代器的例子时,他们总是使用前缀版本。

但是正如你指出的那样,由于可以发生编译器优化,所以实际上没有区别。

请不要让“哪一个更快”这个问题成为决定性的因素。 机会是你永远不会在乎那么多,而且,程序员的阅读时间比机器时间要贵得多。

使用最符合人类阅读的代码。

首先: i++++i之间的区别在C中是可忽略的。


到细节。

1.众所周知的C ++问题: ++i更快

在C ++中,如果i是某种重载增量操作符的对象,则++i更有效。

为什么?
++i ,对象首先增加,随后可以作为const引用传递给任何其他函数。 如果表达式是foo(i++)这是不可能的,因为现在增量需要在调用foo()之前完成,但是旧值需要传递给foo() 。 因此,在执行原始增量操作符之前,编译器不得不复制i 。 额外的构造函数/析构函数调用是不好的部分。

如上所述,这不适用于基本类型。

2.鲜为人知的事实: i++ 可能会更快

如果不需要调用构造函数/析构函数,这在C中总是如此, ++ii++应该同样快,对吗? 不,他们几乎同样快,但是可能会有一些小的差别,大多数其他答复者的方式是错误的。

我怎样才能更快?
关键是数据依赖性。 如果该值需要从内存中加载,则需要对其进行两个后续操作,将其递增并使用。 使用++i ,需要在使用该值之前完成增量。 使用i++ ,使用不依赖于增量,CPU可以与增量操作并行执行使用操作。 这个差别最多只有一个CPU周期,所以它真的是可以忽略不计的,但它在那里。 而这正是许多人所期待的。

简短的回答:

速度方面, i++++i从来没有任何区别。 在这两种情况下,一个好的编译器不应该生成不同的代码。

很长的回答:

其他所有答案都没有提及的是, ++ii++之间的区别只有在找到的表达式中才有意义。

for(i=0; i<n; i++)i++在它自己的表达式中是独立的:在i++之前有一个序列点,在它之后有一个序列点。 因此,唯一生成的机器码是“增加1 ”,并且它是如何按照程序的其他部分进行排序的。 所以如果你把它改成前缀++ ,那丝毫没有关系,你仍然可以只让机器码“增加1 ”。

++ii++之间的区别只在表达式如array[i++] = x;array[++i] = x; 。 有些人可能会争辩说,在这样的操作后缀将会更慢,因为i驻留的寄存器必须稍后重新加载。 但是请注意,编译器可以自由地以任何方式对命令进行排序,只要它不会像C标准所称的那样“破坏抽象机器的行为”。

所以虽然你可能会认为array[i++] = x; 被翻译成机器码为:

  • i值存储在寄存器A中
  • 将数组的地址存储在寄存器B中
  • 添加A和B,将结果存储在A中
  • 在由A表示的这个新地址处,存储x的值。
  • i在寄存器A中存储值//因为在这里额外的指令效率低下,我们已经做了一次。
  • 递增寄存器A.
  • 将寄存器A存储在i

编译器可能会更有效地生成代码,例如:

  • i值存储在寄存器A中
  • 将数组的地址存储在寄存器B中
  • 添加A和B,将结果存储在B.
  • 递增寄存器A.
  • 将寄存器A存储在i
  • … //其余的代码

仅仅因为你作为一个C程序员被训练认为后缀++发生在最后,机器代码就不必这样排序。

所以C中的前缀和后缀++没有任何区别。现在你作为一个C程序员应该是不同的,在某些情况下,用户不一致地使用前缀,而在其他情况下,用户不需要任何理由。 这表明他们不确定C是如何工作的,或者他们对语言有不正确的认识。 这总是一个不好的迹象,这反过来表明,他们正在基于迷信或“宗教教条”,在他们的计划中做出其他可疑的决定。

“前缀++总是比较快”的确是这样一个在C程序员中很常见的假教条。

@Mark即使允许编译器优化(基于堆栈)临时副本的变量,而gcc(在最近的版本中)也这样做,并不意味着所有的编译器都会这样做。

我只用我们当前项目中使用的编译器对其进行了测试,三分之四不对其进行优化。

永远不要假设编译器是正确的,尤其是如果速度可能更快,但从不慢的代码是很容易阅读的。

如果你的代码中没有一个非常愚蠢的实现一个操作符:

Alwas更喜欢++我比我++。

在C中,如果结果未被使用,编译器通常可以优化它们。

但是,在C ++中,如果使用其他类型提供自己的++运算符,则前缀版本可能会比后缀版本更快。 所以,如果你不需要后缀语义,最好使用前缀运算符。

我能想到的情况下,后缀是比前缀增量慢:

设想一个寄存器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++; 语句,编译器可以(并且确实)简单地生成增量指令,而不管后缀或前缀使用情况如何。


作为一个方面说明,我想提一下,有一个b++的表达式不能简单地转换为++b而不需要额外的努力(例如通过添加一个- 1 )。 所以比较两者,如果他们是一些表达的一部分是不是真的有效。 通常情况下,在表达式中使用b++地方不能使用++b ,所以即使++b可能更有效率,也只是错误的。 例外是当然如果表达式是乞求(例如a = b++ + 1;可以更改为a = ++b; )。

我总是喜欢前增量,但是…

我想指出的是,即使在调用operator ++函数的情况下,如果函数被内联,编译器将能够优化临时的。 由于运算符++通常很短并且通常在头文件中实现,所以很可能会被内联。

所以,为了实际的目的,这两种形式的表现可能没有太大的区别。 不过,我总是喜欢预先增量,因为直接表达我想说的内容似乎更好,而不是依靠优化器来解决这个问题。

另外,给optmizer做更少的事情意味着编译器运行得更快。

我的C有点生疏,所以我提前道歉。 快点,我可以理解结果。 但是,我很困惑这两个文件是如何出现在相同的MD5散列。 也许一个for循环运行相同,但不会产生以下2行代码不同的程序集?

 myArray[i++] = "hello"; 

VS

 myArray[++i] = "hello"; 

第一个将值写入数组,然后递增i。 然后我写第二个增量数组。 我不是装配专家,但我不明白这两行不同的代码是如何生成相同的可执行文件的。

只是我的两分钱。