此汇编代码的C / C ++等效项是什么?

时间:2019-04-10 14:26:12

标签: assembly x86 masm

我正在尝试理解此汇编代码,有人可以用C / C ++语言编写该代码吗?

这是代码:

 loc_1C1D40:             ; unsigned int
 push    5
 call    ??_U@YAPAXI@Z   ; operator new[](uint)
 mov     [ebp+esi*4+var_14], eax
 add     esp, 4
 inc     esi
 mov     byte ptr [eax+4], 0
 cmp     esi, 4
 jl      short loc_1C1D40

据我了解,前两行只是调用“ operator new”,它以eax返回一个地址。 此后,“ mov [ebp + esi * 4 + var_14],eax”表示该地址可能保存在某种数组中。 esi增加的原因非常明显。 但是为什么我们要给esp加4?

1 个答案:

答案 0 :(得分:2)

首先进行逐行分析,找出代码的作用

push    5

该指令将常量值“ 5”压入堆栈。为什么?好吧,因为...

call    ??_U@YAPAXI@Z   ; operator new[](uint)

此指令调用operator new[],该参数采用单个uint参数。该参数显然以该代码使用的任何调用约定在堆栈上传递。因此,显然,到目前为止,我们已经调用operator new[]来分配一个大小为5个字节的数组。

在C ++中,它将写为:

BYTE* eax = new BYTE[5];

operator new[]的调用在EAX寄存器中返回其值(指向已分配存储块开头的指针)。这是所有x86调用约定的通用规则-函数始终在EAX寄存器中返回结果。

mov     [ebp+esi*4+var_14], eax

以上代码将结果指针(在mov中返回的指针)存储(EAX)到EBP + (ESI * 4) + var_14寻址的存储位置中。换句话说,它会将ESI寄存器中的值缩放4(大概是uint的大小),加上EBP寄存器的偏移量,然后加上常数var_14

这大致等效于以下伪C ++代码:

void* address = (EBP + (ESI * 4) + var_14);
*address = eax;
add     esp, 4

这将清理堆栈,从而有效地撤消初始的push 5指令。

push将一个32位(4字节)的值压入堆栈,该值堆栈指针,该指针保存在ESP寄存器中(请注意,堆栈在x86上向下增长)。该add指令将堆栈指针(同样是ESP寄存器)增加 4个字节。

以这种方式平衡堆栈是一种优化。您可能等效地编写了pop eax,但是这会产生破坏EAX寄存器中值的附加副作用。

此指令没有直接的C ++等效项,因为它只是在做簿记工作,通常会被高级语言隐藏。

inc     esi

这会将ESI寄存器的值加1。等效于:

esi += 1;
mov     byte ptr [eax+4], 0

这将常数0存储在EAX + 4的BYTE大小的存储块中。它对应于以下伪C ++:

BYTE* ptr = (eax + 4);
*ptr = 0;
cmp     esi, 4

这会将ESI寄存器的值与常数4进行比较。CMP指令实际上将标志设置为好像已进行了减法。

因此,后续说明:

jl      short loc_1C1D40
如果ESI寄存器的值小于 4,则

有条件地跳转。

比较跳转是高级语言(例如forwhile循环)中循环构造的标志。


将所有内容放在一起,您会得到类似的东西:

void Foo(char** var_14)
{
    for (int esi = 0; esi < 4; ++esi)
    {
        var_14[esi] = new char[5];
        var_14[esi][4] = 0;
    }
}

那是不完全正确的。从编译的程序集中重构原始的C或C ++代码,就像从地面牛肉饼重构原始的母牛一样。

但这很好。实际上,if you compile the above function in MSVC, optimizing for speed and targeting 32-bit x86, you get the following assembly generated

void Foo(char**) PROC
        push    esi
        push    edi
        mov     edi, DWORD PTR _var_14$[esp+4]
        xor     esi, esi
$LL4@Foo:
        push    5
        call    void * operator new[](unsigned int)  ; operator new[]
        mov     DWORD PTR [edi+esi*4], eax
        add     esp, 4
        inc     esi
        mov     BYTE PTR [eax+4], 0
        cmp     esi, 4
        jl      SHORT $LL4@Foo
        pop     edi
        pop     esi
        ret     0
void Foo(char**) ENDP

与问题中的内容几乎完全相同,假设您忽略了序言和结语(无论如何您都没有在问题中显示)。

主要区别在于,编译器正在对MOV指令应用相当明显的循环提升优化。而不是原始代码:

mov   [ebp + esi * 4 + var_14], eax

相反,它会在序言中预先计算esp + var_14,并将结果缓存到空闲的EDI寄存器中:

mov   edi, DWORD PTR _var_14$[esp + 4]

允许循环内的加载指令很简单:

mov   DWORD PTR [edi + esi * 4], eax

我不知道您的代码为什么不执行此操作,或者为什么它使用EBP来保存偏移量。