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

use*_*557 7 c x86 assembly

可能重复:
为什么发出这样复杂的代码用于将有符号整数除以2的幂?

背景

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

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

Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.21003.01 for 80x86
Run Code Online (Sandbox Code Playgroud)

C代码(sandbox.c)

int mainCRTStartup()
{
    int x=5;int y=1024;
    while(x) { x--; y/=2; }
    return x+y;
}
Run Code Online (Sandbox Code Playgroud)

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

cl /c /O2 /Oy- /MD sandbox.c
link /NODEFAULTLIB /MANIFEST:NO /SUBSYSTEM:CONSOLE sandbox.obj
Run Code Online (Sandbox Code Playgroud)

在OllyDgb中解雇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
Run Code Online (Sandbox Code Playgroud)

检查

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.
Run Code Online (Sandbox Code Playgroud)

这部分困扰我:

0040100A  |. 8D9B 00000000  LEA EBX,DWORD PTR DS:[EBX]
00401010  |> 99             /CDQ
00401011  |. 2BC2           |SUB EAX,EDX
Run Code Online (Sandbox Code Playgroud)

您可以将其全部删除,EAX和ECX的值最终将保持不变.那么,这些指示的重点是什么?

AnT*_*AnT 11

整个东西

00401010  |> 99             /CDQ
00401011  |. 2BC2           |SUB EAX,EDX
00401013  |. D1F8           |SAR EAX,1
Run Code Online (Sandbox Code Playgroud)

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

诀窍很简单:对于负y号扩展,将放置一个11111...1in 的模式,EDX实际上-1是2的补码表示.如果原始值为负SUB,EAX则以下将有效地添加1 y.如果原来y是正(或0),EDX将持有0的符号扩展后EAX会保持不变.

换句话说,当您y /= 2使用signed进行编写时y,编译器会生成执行更多类似操作的代码

y = (y < 0 ? y + 1 : y) >> 1;
Run Code Online (Sandbox Code Playgroud)

或更好

y = (y + (y < 0)) >> 1;
Run Code Online (Sandbox Code Playgroud)

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

PS我不确定该LEA指令的目的是什么.这确实是一个无操作.但是,我怀疑这可能只是插入代码中的占位符指令以进行进一步修补.如果我没记错的话,MS编译器有一个选项,强制在每个函数的开头和结尾插入占位符指令.今后该指令可以改写通过与该修补CALLJMP将执行补丁代码指令.LEA选择此特定原因仅仅是因为它产生了正确长度的无操作占位符指令.当然,它可能是完全不同的东西.


int*_*jay 5

lea ebx,[ebx]只是一个NOP操作.它的目的是在内存中对齐循环的开头,这将使它更快.正如您在此处所看到的,循环的开始在地址0x00401010处开始,由于此指令,该地址可被16整除.

CDQSUB EAX,EDX操作确保该司将向零舍入负数-否则SAR将磨圆下来,让不正确的结果为负数.

  • jn:EBX保存值的事实是无关紧要的,因为该指令不会将其与任何内容进行比较或更改它.它本可以访问任何其他注册表.如果查看Visual Studio创建的代码,您将在大多数内部循环之前看到这种代码 - 并且循环的开头将与16(或8个)字节对齐. (5认同)
  • 你自己错了.`lea ebx,[ebx]`相当于`mov ebx,ebx` - 它什么都不做.不检查ebx寄存器的值. (4认同)