Based on the assembler listings, a C++ object is locally allocated on the stack without constructing the object in the caller routine and the callee takes the address to the allocated stack space and invokes the constructor. How is this possible using C++? The compiler used is Watcom C/C++ 10.5 from 1995, from way before ISO C++98's arrival. The compiler uses Watcom's register calling convention so first argument is passed in EAX, second in EDX and return value is passed back in EAX. EBP is used as stack frame pointer.
The caller function is a class A method which reserves space for a class B locally allocated object on the stack frame. Even though the class B object resides on a dword only it is a complex object that internally allocates lots of stuff to the heap when the constructor is called. It is similar to a smart pointer to a smart object. Any ways what I wanted to highlight is that class B is far from a simple struct. ObjectA on the stack frame is class A's this
pointer passed to the Caller method in EAX. The caller sets up the arguments to callee. Callee's second argument is an address to the ObjectB stack space.
Caller function:
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_
...
The callee function is also a class A method so first argument is the this
pointer. The second argument is the address to a memory space which is passed to the constructor of class B. The construction is not based on assignment and copy constructor, it is a call to the default (implicit?) constructor of class B without allocating memory for the object being constructed.
Callee function:
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
The assembly listings are generated by IDA Freeware 7.0.
The following statements can be made:
- Operator new is not used to allocate the class B object onto the stack.
- The placement new operator is not used within the callee function to omit allocation at construction. That would have generated totally different code and would have emitted a dummy like operator new for the placement new use case.
- In 1995 there was no std::allocator and any ways it would also require placement new.
- I do not think that the original authors simply created a dword and casted it as I assume as professionals they should have known about dangers of violating stack and object alignment rules as well as I do not think that they called directly the constructors in some wicked ways.
- I tried a lot of stuff to replicate the C++ code and build it again in Watcom C/C++ 10.5 compiler in MS-DOS to get to the same disassembly listing or one that is close to the original, but utterly failed.
The construct is used in a lot of places within the original program, redesigning the code base would be very difficult.
Any new ideas would be highly welcome, thanks in advance for any help.