32

在 C 语言中,为什么n++执行速度比n=n+1?

(int n=...;  n++;)
(int n=...;  n=n+1;)

我们的老师在今天的课上问了这个问题。(这不是作业)

4

10 回答 10

102

如果您正在使用“石器时代”编译器,那将是正确的......

“石器时代”的情况下:比 机器通常拥有的速度
++n更快n++n=n+1
increment xadd const to x

  • 如果是n++,您将只有 2 次内存访问(读取 n、inc n、写入 n)
  • 如果是n=n+1,您将有 3 次内存访问(读取 n,读取 const,添加 n 和 const,写入 n)

但是今天的编译器会自动转换n=n+1++n,它会做的比你想象的还要多!!

同样在今天的乱序处理器上——尽管是“石器时代”编译器的情况——在许多情况下运行时可能根本不会受到影响!!

有关的

于 2010-05-21T18:59:38.603 回答
42

在 x86 的 GCC 4.4.3 上,无论有没有优化,它们都编译为完全相同的汇编代码,因此执行时间相同。正如您在程序集中看到的那样,GCC 只是简单地转换n++n=n+1,然后将其优化为单指令添加(在 -O2 中)。

您的讲师建议n++更快,仅适用于非常旧的非优化编译器,这些编译器不够聪明,无法为n = n + 1. 这些编译器在 PC 世界中已经过时多年,但仍然可以在奇怪的专有嵌入式平台上找到。

C代码:

int n;

void nplusplus() {
    n++;
}

void nplusone() {
    n = n + 1;
}

输出汇编(无优化):

    .file   "test.c"
    .comm   n,4,4
    .text
.globl nplusplus
    .type   nplusplus, @function
nplusplus:
    pushl   %ebp
    movl    %esp, %ebp
    movl    n, %eax
    addl    $1, %eax
    movl    %eax, n
    popl    %ebp
    ret
    .size   nplusplus, .-nplusplus
.globl nplusone
    .type   nplusone, @function
nplusone:
    pushl   %ebp
    movl    %esp, %ebp
    movl    n, %eax
    addl    $1, %eax
    movl    %eax, n
    popl    %ebp
    ret
    .size   nplusone, .-nplusone
    .ident  "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
    .section    .note.GNU-stack,"",@progbits

输出程序集(使用 -O2 优化):

    .file   "test.c"
    .text
    .p2align 4,,15
.globl nplusplus
    .type   nplusplus, @function
nplusplus:
    pushl   %ebp
    movl    %esp, %ebp
    addl    $1, n
    popl    %ebp
    ret
    .size   nplusplus, .-nplusplus
    .p2align 4,,15
.globl nplusone
    .type   nplusone, @function
nplusone:
    pushl   %ebp
    movl    %esp, %ebp
    addl    $1, n
    popl    %ebp
    ret
    .size   nplusone, .-nplusone
    .comm   n,4,4
    .ident  "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
    .section    .note.GNU-stack,"",@progbits
于 2010-05-21T19:25:38.353 回答
14

编译器将优化n + 1为虚无。

你的意思是n = n + 1

如果是这样,它们将编译为相同的程序集。(假设优化已开启并且它们是语句,而不是表达式)

于 2010-05-21T18:58:59.037 回答
5

谁说有?您的编译器确实将其全部优化,使其成为一个有争议的问题。

于 2010-05-21T18:58:48.277 回答
3

现代编译器应该能够将这两种形式识别为等效形式,并将它们转换为最适合您的目标平台的格式。此规则有一个例外:具有副作用的变量访问。例如,如果n是某个内存映射的硬件寄存器,则对其进行读取和写入可能不仅仅是传输数据值(例如,读取可能会清除中断)。您可以使用volatile关键字让编译器知道它需要小心优化对 的访问n,在这种情况下,编译器可能会从n++(增量操作)和n = n + 1(读取、添加和存储操作)生成不同的代码。但是对于普通变量,编译器应该将两种形式优化为相同的东西。

于 2010-05-21T19:55:48.747 回答
2

它不是真的。编译器将针对目标架构进行特定的更改。像这样的微优化通常有可疑的好处,但重要的是,肯定不值得程序员花时间。

于 2010-05-21T19:09:48.590 回答
2

实际上,原因是运算符对后缀的定义与对前缀的定义不同。 ++n将递增“n”并返回对“n”的引用,而n++将递增“n”将返回“n”的const副本。因此,该短语n = n + 1将更有效率。但我不得不同意上面的海报。好的编译器应该优化掉一个未使用的返回对象。

于 2010-05-21T19:32:41.373 回答
2

在 C 语言中,n++表达式的副作用在定义上等同于表达式的副作用n = n + 1。由于您的代码仅依赖于副作用,因此很明显正确的答案是这些表达式始终具有完全相同的性能。(不管编译器中的任何优化设置,顺便说一句,因为这个问题与任何优化完全无关。)

只有当编译器有意(并且恶意地!)试图引入这种差异时,这些表达式的性能上的任何实际差异才有可能。但在这种情况下,它当然可以采用任何一种方式,即编译器的作者想要扭曲它的任何一种方式。

于 2010-05-21T19:55:46.127 回答
1

I think it's more like a hardware question rather than software... If I remember corectly, in older CPUs the n=n+1 requires two locations of memory, where the ++n is simply a microcontroller command... But I doubt this applies for modern architectures...

于 2010-12-24T00:09:56.243 回答
0

All those things depends on compiler/processor/compilation directives. So make any assumptions "what is faster in general" is not a good idea.

于 2010-12-23T21:52:45.657 回答