C编程:从汇编的角度看++i和i=i+1的区别?

17

这是一道面试题。我说它们是一样的,但是这被裁定为不正确的回答。从汇编语言的角度来看,有什么想象得到的区别吗?我使用默认的gcc优化和-S编译了两个简短的C程序,以查看汇编输出,它们是相同的。


1
面试官说什么是正确答案? - anon
他可能把++i和i++搞混了? - starblue
6
即使没有赋值运算,但使用++i和i++,在有好的优化器的情况下,也应该编译为相同的代码,对吗? - spender
6个回答

20
面试官可能希望得到这样的答案:

i=i+1 需要加载 i 的值,加上 1 ,再将结果存回 i。相比之下,++i 只需使用一条汇编指令递增该值,因此理论上它可能更高效。不过,大多数编译器会优化掉这种差异,生成的代码完全相同。

顺便说一下,你懂得如何查看汇编代码,让你比我在过去面试的 90% 的人都优秀。安慰自己的是,你不必和那些毫无头绪的失败者共事。


3
从汇编语言的角度来看,这是正确的,但是所有x86处理器对于这两个指令序列都会执行相同的操作——inc [memadr]需要将值加载到隐藏寄存器中。然而,会生成不同数量的代码。 - sharptooth
1
我期望一个不错的编译器能够看到左侧和右侧是同一个 i,并生成相同的代码。 - Nathan Fellman
2
现代SSA编译器不会重写整个语句吗?第一条语句变成了i_2=i_1+1,第二条也变成了i_2=i_1+1。很有可能i_1和i_2变量没有被分配到相同的寄存器。 - MSalters
1
对于int i,如果您的编译器在这些语句中产生了次优汇编代码(当然是启用了优化的情况下),那么就是一个错误,应该进行修复。我们关注的主流ISA编译器(如gcc、clang、MSVC、ICC)都没有问题。不能指望这个C语句编译成任何特定的汇编语言:取决于周围的代码,循环可能会展开,或者增量可以折叠到某些其他操作中。 - Peter Cordes
1
对于_Atomic int i,它们并不相同,但这基本上就像C++中的重载运算符。 - Peter Cordes

10

看起来你是对的,而他们是错的。我在一次面试中也遇到了类似的问题,我回答了正确的答案,然而却被认为是错误的。

我自信地与我的面试官争论这个问题,但显然他非常反感我的鲁莽。虽然我没有得到那份工作,但是想想在一个“什么都知道”的人下工作也并不是很理想。


3
值得争论 - 我有同样的经历,指出了面试官的错误之处和原因,最终获得了这份工作。 - anon
2
同意。总是给出正确的答案比你认为面试官想要听到的更好。好的面试官会想要雇佣比他们聪明的人。 - Kristopher Johnson

8

您可能是正确的。一个天真的编译器可能会这样做:

++i to inc [ax]

并且

i = i + 1 to add [ax], 1

但任何半明智的编译器都会优化将1添加到第一个版本。

这一切都假设相关架构具有inc和add指令(像x86一样)。


我会在面试中这样评论。如果有增量指令,那么++i肯定会使用它,而i = i + 1可能不会在一个简单的编译器中使用增量指令,但是一个好的编译器应该会进行优化以实现相同的效果。 - workmad3
两个汇编操作符都无效,方括号意味着通过AX访问内存插槽。同时也没有inc ax指令 - 必须使用add 1 to AX。 - mP.
@mP。两种都是正确的。inc [ax] 表示增加 ax 所寻址的内存,而 inc ax 表示增加 ax 本身。 - Nathan Fellman
1
值得注意的是,++i(或i ++,或i = i + 1)并不总是映射到“inc”指令。如果i是指向int的指针,则++i将与add i, 4相同,因为i需要指向下一个宽度为4个字节的int。如果i指向一个长整型变量,则++i将出于同样的原因与add i,8相同。 - Nathan Fellman
[ax] 不是有效的 16 位寻址模式。[bx] 是,[eax] 是一个有效的 32 位寻址模式。无论如何,有趣的事实是,在现代英特尔 CPU 上,add [mem], 1addinc 更有效率的一个例子。 INC指令 vs ADD 1: 有关吗? 但如果您的优化编译器生成的汇编代码依赖于这种源码差异,那么您的编译器就存在错误(错过了优化的漏洞)。像 inc 这样的 peephole 应该由编译器后端自行处理,根据情况使用或不使用(如果它们实际上更慢)。 - Peter Cordes
显示剩余2条评论

7
为了帮助面试官,上下文至关重要。i的类型是什么?我们是在谈论C还是C++(或其他类似C的语言)?你是否被给予以下信息:
++i;
i = i + 1;

还有更多的背景信息吗?

如果我被问到这个问题,我的第一反应会是“它是否是易失性的?” 如果答案是肯定的,那么差异就很大。 如果不是,差别很小,仅仅是语义上的区别,实际上并没有什么影响。 这一点可以从解析树的差异和生成的子树的最终含义得出证明。

所以听起来你在实用方面做得很好,但在语义/批判性思考方面可能存在问题。

在没有上下文的情况下攻击面试官,我会想知道问题的目的是什么。 如果我问这个问题,我想用它来了解候选人是否了解微妙的语义差异,如何生成解析树,如何进行批判性思考等等。 我通常会问我的受访者一个C语言问题,几乎每个候选人都会回答错误-这是有意而为之的。 实际上,我并不在意问题的答案,我在意的是我将与候选人一起走过的旅程,这告诉我比一道琐碎问题的对错更多。


那是知识产权。 - plinth
你认为 volatile int i 和普通的 int i 有什么区别?大多数编译器都会将它们编译成相同的代码。C标准/ as-if规则并没有指定使用单个指令进行内存目标增量(原子 wrt.中断但不是其他核心)。GCC 错过了 使用单个内存目标指令执行 ++ii=i+1 的优化,但clang对于volatile和非volatile都可以实现:https://godbolt.org/z/j46qGF。因此,在两个编译器中,`++i` 和 i=i+1 之间仍然没有区别,只有在volatile和非volatile之间才有区别。 - Peter Cordes
有没有嵌入式编译器,其中volatile使得++i有很大的不同? - Peter Cordes

3

在C++中,这取决于i是一个int还是一个对象。如果它是一个对象,它可能会生成一个临时实例。


真,但问题规定是用C语言。 - Nathan Fellman
假设对象的类重载了++运算符。 - ardsrk
据我所知,如果++没有被重载,这是正确的。 - Johan Kotlinski

0

这里的上下文很重要,因为在优化的发布版本中,如果 i++ 可用于简单的 [inc eax],编译器将对其进行优化。而像 int some_int = i++ 这样的语句需要先将 i 的值存储在 some_int 中,然后才能递增 i。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接