c - 为什么 MSVC 调试模式会为一个空的 if() 主体而不是另一个(i++ 与 ++i)遗漏 cmp/jcc?
问题描述
我正在使用 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)
中,程序不观察“添加” i 是否满足条件。
但是在 中if (i++ == 4)
,程序将“前一个” i 保存到堆栈中,然后进行增量。之后,程序将“前一个”i 与常数整数 4 进行比较。
两个C代码不同的原因是什么?它只是编译器的机制吗?更复杂的代码会有所不同吗?
我试图用谷歌找到这个,但我没能找到差异的根源。我必须了解“这只是编译器行为”吗?
解决方案
正如 Paul 所说,该程序没有可观察到的副作用,并且启用优化后,MSVC 或任何其他主要编译器(gcc/clang/ICC)都将编译main
为简单的xor eax,eax
/ ret
。
i
的值永远不会转义函数(不存储到全局或返回),因此可以完全优化它。即使是这样,恒定传播在这里也是微不足道的。
这只是一个怪癖/实现细节,MSVC 的调试模式反优化代码生成决定不在cmp/jcc
空if
主体上发出 a;即使在调试模式下也对调试毫无帮助。这将是一个跳转到它所经过的相同地址的分支指令。
调试模式代码的要点是您可以单步执行源代码行,并使用调试器修改 C 变量。并不是说 asm 是 C 到 asm 的字面和忠实音译。(而且编译器可以快速生成它,而无需在质量上花费任何精力,以加快编辑/编译/运行周期。) 为什么 clang 会使用 -O0 产生低效的 asm(对于这个简单的浮点求和)?
编译器的代码生成到底有多脑残不取决于任何语言规则。没有实际的标准来定义编译器在调试模式下必须做什么,就实际使用分支指令为空if
体而言。
显然,对于您的编译器版本,i++
后增量足以让编译器忘记循环体是空的?
我无法在 Godbolt 编译器资源管理器上使用 32 或 64 位模式的MSVC 19.0 或 19.10 重现您的结果。(VS2015 或 VS2017)。或任何其他 MSVC 版本。我根本没有从 MSVC、ICC 或 gcc 获得条件分支。
MSVC 确实实现i++
了旧值的实际存储到内存,就像你展示的那样。太坏了。GCC-O0
显着提高了调试模式代码的效率。当然,仍然相当脑残,但在一个单一的声明中,有时它的糟糕程度要小得多。
不过,我可以用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
推荐阅读
- swift - 具有通用成功类型的结果类型
- c# - 在 C# 中生成 AD 令牌
- android - 在运行 androidTests 中找不到符号 R
- node.js - 有没有办法使用 AWS CLI 获取 S3 对象的位置(公共 URL)?
- docker - 使用 selenium 网格运行 kubernetes
- jquery - 为什么 jQuery 验证函数在 ASP.NET MVC Core 应用程序中不起作用?
- mysql - 使用正则表达式匹配以逗号分隔的数字的数字
- c# - ASP.NET MVC 过滤器(相同类型)是否按照它们包含在代码中的顺序执行,并且可以保证吗?
- python - Int 类型的 z3 python 中的未知结果
- internet-explorer - Internet explorer -11 -Developer tools(F12) ->Emulation tab ->Document mode is not display IE 11 option