c - 检查 Visual Studio C++ 编译器生成的代码,第 1 部分

标签 c assembly x86

<分区>

Possible Duplicate:
Why is such complex code emitted for dividing a signed integer by a power of two?

背景

我只是通过检查编译器生成的二进制代码来学习 x86 asm。

Visual Studio 2010 beta 2 中使用 C++ 编译器编译的代码.

Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.21003.01 for 80x86

C 代码 (sandbox.c)

int mainCRTStartup()
{
    int x=5;int y=1024;
    while(x) { x--; y/=2; }
    return x+y;
}

使用 Visual Studio 命令提示符编译它

cl /c /O2 /Oy- /MD sandbox.c
link /NODEFAULTLIB /MANIFEST:NO /SUBSYSTEM:CONSOLE sandbox.obj

OllyDgb 中的 Disasm sandbox.exe

下面从入口开始

00401000 >/$ B9 05000000    MOV ECX,5
00401005  |. B8 00040000    MOV EAX,400
0040100A  |. 8D9B 00000000  LEA EBX,DWORD PTR DS:[EBX]
00401010  |> 99             /CDQ
00401011  |. 2BC2           |SUB EAX,EDX
00401013  |. D1F8           |SAR EAX,1
00401015  |. 49             |DEC ECX
00401016  |.^75 F8          \JNZ SHORT sandbox.00401010
00401018  \. C3             RETN

考试

MOV ECX, 5          int x=5;
MOV EAX, 400        int y=1024;
LEA  ...            // no idea what LEA does here. seems like ebx=ebx. elaborate please.
                    // in fact, NOPing it does nothing to the original procedure and the values.

CQD                 // sign extends EAX into EDX:EAX, which here: edx = 0. no idea why.
SUB EAX, EDX        // eax=eax-edx, here: eax=eax-0. no idea, pretty redundant. 
SAR EAX,1           // okay, y/= 2
DEC ECX             // okay, x--, sets the zero flag when reaches 0.
JNZ ...             // okay, jump back to CQD if the zero flag is not set.

这部分困扰着我:

0040100A  |. 8D9B 00000000  LEA EBX,DWORD PTR DS:[EBX]
00401010  |> 99             /CDQ
00401011  |. 2BC2           |SUB EAX,EDX

你可以全部nop,最后EAX和ECX的值将保持不变。那么,这些说明的意义何在?

最佳答案

一切

00401010  |> 99             /CDQ
00401011  |. 2BC2           |SUB EAX,EDX
00401013  |. D1F8           |SAR EAX,1

代表y/= 2。您会看到,独立的 SAR 不会按照编译器作者的意图执行有符号整数除法。 C++98 标准建议将带符号的整数除法向 0 舍入,而 SAR 单独将向负无穷大舍入。 (允许向负无穷大舍入,选择留给实现)。为了将负操作数舍入到 0,使用了上述技巧。如果您使用无符号类型而不是有符号类型,那么编译器将只生成一个移位指令,因为负除法问题不会发生。

技巧非常简单:对于负号 y 扩展将在 EDX 中放置一个 11111...1 的模式,即实际上 -1 以 2 的补码表示。如果原始 y 值为负,则以下 SUB 将有效地向 EAX 加 1。如果原始 y 为正(或 0),则 EDX 将在符号扩展和 EAX 之后保持 0将保持不变。

换句话说,当您使用带符号的 y 编写 y/= 2 时,编译器生成的代码更类似于以下内容

y = (y < 0 ? y + 1 : y) >> 1;

或者,更好

y = (y + (y < 0)) >> 1;

请注意,C++ 标准不要求除法结果向零舍入,因此即使对于有符号类型,编译器也有权只进行一次移位。但是,编译器通常会遵循建议向零舍入(或提供控制行为的选项)。

P.S. 我不确定 LEA 指令的目的是什么。这确实是一个空操作。但是,我怀疑这可能只是插入到代码中用于进一步修补的占位符指令。如果我没记错的话,MS 编译器有一个选项可以强制在每个函数的开头和结尾插入占位符指令。将来,补丁程序可以使用执行补丁代码的 CALLJMP 指令覆盖该指令。选择这个特定的 LEA 只是因为它产生了正确长度的无操作占位符指令。当然,也可以是完全不同的东西。

关于c - 检查 Visual Studio C++ 编译器生成的代码,第 1 部分,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1658186/

相关文章:

python - C 或 Python 中的双峰分布

c# - C#Unity和C服务器中的TcpClient

gcc 内联 asm bug - 忽略参数

assembly - 在 MASM32 程序集中打印 unicode 字符

c - 程序集,无法将数组值从堆栈添加到寄存器

c - 了解 C 中的 x86 ASM 函数

c - 在字符串回文中找到缺失的字符

c - 用通用参数包装算法的最安全和最易读的方法?

linux - 如何在 Linux 程序集中读取和显示一个值?

c++ - 将单个 float 移动到 xmm 寄存器