此汇编代码的 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
的指针)存储 (mov
es) 到 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.
比较和跳转是高级语言中循环结构的标志,例如 for
或 while
循环。
把它们放在一起,你有这样的东西:
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++ 代码很像用碎牛肉饼重构原始奶牛。
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
来保存偏移量。
我正在尝试理解这段汇编代码,有人可以帮助我用 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
的指针)存储 (mov
es) 到 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.
比较和跳转是高级语言中循环结构的标志,例如 for
或 while
循环。
把它们放在一起,你有这样的东西:
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++ 代码很像用碎牛肉饼重构原始奶牛。
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
来保存偏移量。