此汇编代码的 C/C++ 等价物是什么?

What is the C/C++ equivalent for this assembly code?

我正在尝试理解这段汇编代码,有人可以帮助我用 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" 其中 return eax 中的一个地址。 此后,"mov [ebp+esi*4+var_14], eax" 表示地址可能保存在某种数组中。 esi 递增的原因很明显。 但是为什么我们要给 esp 添加 4?

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

push    5

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

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

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

在 C++ 中,将写为:

BYTE* eax = new BYTE[5];

operator new[]的调用return是它在EAX寄存器中的值(指向已分配内存块开头的指针)。这是所有 x86 调用约定的通用规则——函数总是 return 它们的结果在 EAX 寄存器中。

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

以上代码将结果指针(returned in EAX 的指针)存储 (moves) 到 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 来保存偏移量。