2

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.

1

The pattern you describe sounds like the standard pattern for returning objects by value. So you are looking for

class A {
    B callee()
    {
        return B();
    }
    void caller()
    {
        B b = callee();
    }
}
1
  • This is it! Thanks a lot. Interestingly the compiler passes the address of b to callee() as a hidden argument. This argument is the first, unless callee is a class method in which case this pointer goes to first argument and second becomes the address of b.
    – Nad
    Aug 27 at 12:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.