c - 为什么 MSVC Debug模式会为一个空的 if() 主体而不是另一个(i++ vs.++i)省略 cmp/jcc?

标签 c assembly visual-c++ reverse-engineering

我在用一台AMD64位的电脑(Intel Pentium Gold 4415U)对比一些C语言转过来的汇编指令(当然是反汇编)。

在 Windows 10 中,我使用了 Visual Studio 2017(15.2) 及其 C 编译器。 我的示例代码如下所示:

int main() {
    int i = 0;
    if(++i == 4);
    if(i++ == 4);
    return 0;
}

反汇编如下:

mov         eax,dword ptr [i]  // if (++i == 4);
inc         eax  
mov         dword ptr [i],eax  

mov         eax,dword ptr [i]  // if (i++ == 4);
mov         dword ptr [rbp+0D4h],eax    ; save old i to a temporary
mov         eax,dword ptr [i]  
inc         eax  
mov         dword ptr [i],eax  
cmp         dword ptr [rbp+0D4h],4      ; compare with previous i
jne         main+51h (07FF7DDBF3601h)  
mov         dword ptr [rbp+0D8h],1  
jmp         main+5Bh (07FF7DDBF360Bh)  
*mov         dword ptr [rbp+0D8h],0

07FF7DDBF3601 转到最后一行指令(*)。
07FF7DDBF360B 转到“返回 0;”。

if (++i == 4) 中,程序不会观察 'added' i 是否满足条件。

但是在 if (i++ == 4) 中,程序将“前一个”i 保存到堆栈,然后进行递增。之后,程序将“previous”i 与常量整数 4 进行比较。

两个C代码不同的原因是什么?它只是编译器的机制吗?更复杂的代码会有所不同吗?

我试图用谷歌找到这个,但是我没能找到差异的根源。我必须理解“这只是编译器行为”吗?

最佳答案

正如 Paul 所说,该程序没有可观察到的副作用,并且启用优化的 MSVC 或任何其他主要编译器 (gcc/clang/ICC) 将编译 main简单地 xor eax,eax/ret .

i的值永远不会逃脱函数(不存储到全局或返回),因此它可以完全优化掉。即使是这样,持续传播在这里也是微不足道的。


MSVC 的 Debug模式反优化代码生成决定不发出 cmp/jcc 只是一个怪癖/实现细节在一个空的if body ;即使在 Debug模式下也根本无助于调试。这将是一个分支指令,跳转到它下降到的相同地址。

Debug模式代码的要点是您可以单步执行源代码行,并使用调试器修改C 变量。并不是说 asm 是 C 到 asm 的字面和忠实的音译。 (而且编译器可以快速生成它,无需在质量上花费任何精力,以加快编辑/编译/运行周期。)Why does clang produce inefficient asm with -O0 (for this simple floating point sum)?

编译器的代码生成到底有多脑残并不取决于任何语言规则;就实际为空 if 使用分支指令而言,没有实际的标准来定义编译器在 Debug模式下必须做什么。 body 。


显然与您的编译器版本有关,i++后增量是否足以让编译器忘记循环体是空的?

我无法使用 MSVC 19.0 或 19.10 重现您的结果 on the Godbolt compiler explorer, with 32 or 64-bit mode 。 (VS2015 或 VS2017)。或任何其他 MSVC 版本。我根本没有从 MSVC、ICC 或 gcc 获得条件分支。

MSVC 确实实现了 i++不过,就像您展示的那样,将旧值实际存储到内存中。太坏了。海合会 -O0显着提高 Debug模式代码的效率。当然仍然相当脑残,但在一个单一的陈述中,有时情况要好得多。

不过,我可以用 clang 重现它! (但它为两个 if s 分支):

# clang8.0 -O0
main:                                   # @main
        push    rbp
        mov     rbp, rsp
        mov     dword ptr [rbp - 4], 0       # default return value

        mov     dword ptr [rbp - 8], 0       # int i=0;

        mov     eax, dword ptr [rbp - 8]
        add     eax, 1
        mov     dword ptr [rbp - 8], eax
        cmp     eax, 4                       # uses the i++ result still in a register
        jne     .LBB0_2                      # jump over if() body
        jmp     .LBB0_2                      # jump over else body, I think.
.LBB0_2:

        mov     eax, dword ptr [rbp - 8]
        mov     ecx, eax
        add     ecx, 1                       # i++ uses a 2nd register
        mov     dword ptr [rbp - 8], ecx
        cmp     eax, 4
        jne     .LBB0_4
        jmp     .LBB0_4
.LBB0_4:

        xor     eax, eax                     # return 0

        pop     rbp                          # tear down stack frame.
        ret

关于c - 为什么 MSVC Debug模式会为一个空的 if() 主体而不是另一个(i++ vs.++i)省略 cmp/jcc?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55533715/

相关文章:

c++ - 根据 MSVC,具有 volatile 成员的结构不再是 POD

c++ - WhiteThresholdImage 魔法

c - C 位运算的行为

iphone - 如何使用 C API 使用 SQLite3 导入命令?

c - 多种类型的归并排序

c - C 中的二进制搜索无法正常工作

c - 汇编器 + C 或我无法让中断工作

linux - 如何从 DX :AX/EDX:EAX? 获取值

linux - NASM中的printf最简单的工作示例失败

visual-c++ - fatal error C1017 : invalid integer constant expression