根据汇编程序列表,C++ 对象在堆栈上本地分配,而无需在调用例程中构造对象,被调用方将地址带到分配的堆栈空间并调用构造函数。这怎么可能使用 C++?使用的编译器是 1995 年的 Watcom C/C++ 10.5,早在 ISO C++98 出现之前。编译器使用 Watcom 的寄存器调用约定,因此第一个参数在 EAX 中传递,第二个在 EDX 中,返回值在 EAX 中传递回。EBP 用作堆栈帧指针。
调用函数是一个 A 类方法,它在堆栈帧上为 B 类本地分配的对象保留空间。尽管 B 类对象仅驻留在 dword 上,但它是一个复杂的对象,在调用构造函数时会在内部将大量内容分配给堆。它类似于指向智能对象的智能指针。我想强调的任何方式是 B 类远不是一个简单的结构。堆栈帧上的 ObjectA 是this传递给 EAX 中 Caller 方法的类 A 的指针。调用者为被调用者设置参数。Callee 的第二个参数是 ObjectB 堆栈空间的地址。
调用函数:
MethodCaller_ proc near
ObjectB = dword ptr -8
ObjectA = dword ptr -4
push 32
call __CHK
push ebx
push ecx
push esi
push edi
push ebp
mov ebp, esp
sub esp, 8
mov [ebp+ObjectA], eax
lea edx, [ebp+ObjectB]
mov eax, [ebp+ObjectA]
call MethodCallee_
...
被调用函数也是 A 类方法,因此第一个参数是this指针。第二个参数是传递给类 B 的构造函数的内存空间的地址。构造不是基于赋值和复制构造函数,它是对类 B 的默认(隐式?)构造函数的调用,而不为它分配内存正在构造的对象。
调用函数:
MethodCallee_ proc near
ObjectA = dword ptr -8
ObjectB = dword ptr -4
push 32
call __CHK
push ebx
push ecx
push esi
push edi
push ebp
mov ebp, esp
sub esp, 8
mov [ebp+ObjectA], eax
mov [ebp+ObjectB], edx
mov eax, [ebp+ObjectB]
call ObjectB_Ctor_
mov eax, [ebp+ObjectB]
mov esp, ebp
pop ebp
pop edi
pop esi
pop ecx
pop ebx
retn
MethodCallee_ endp
程序集列表由 IDA 免费软件 7.0 生成。
可以作出以下声明:
- 运算符 new 不用于将 B 类对象分配到堆栈上。
- 在被调用函数中不使用放置 new 运算符来省略构造时的分配。那会生成完全不同的代码,并且会为放置新用例发出一个类似 operator new 的虚拟代码。
- 1995 年没有 std::allocator 并且它也需要放置新的任何方式。
- 我不认为原始作者只是简单地创建了一个 dword 并将其转换为我认为作为专业人士他们应该知道违反堆栈和对象对齐规则的危险,并且我不认为他们以某些邪恶的方式直接调用构造函数.
- 我尝试了很多东西来复制 C++ 代码并在 MS-DOS 的 Watcom C/C++ 10.5 编译器中再次构建它以获得相同的反汇编列表或接近原始的反汇编列表,但完全失败了。
该构造在原始程序中的很多地方使用,重新设计代码库将非常困难。
任何新想法都将受到高度欢迎,在此先感谢您的帮助。