Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Crash in constructor with using virtual inheritance and delegating constructors

struct D
{
    virtual void m() const = 0;
};

struct D1 : public virtual D { };

struct D2 : public virtual D { };

struct B : public D2
{
    B() { }

    B(int val) : B() { }

    void m() const { }
};

struct A : public B, public D1
{
    A() : B(0) { }
};

int main()
{
    A a;
    return 0;
}

I get crash with MSVC 2013 compiler with above code. It runs without crash when compiled with GCC 4.7.2. Hierarchy of classes is depicted below.

         D
       /  \
     D1    D2
      |     |
       \    B
        \  /
         A

This is a bug in MS compiler or I made a mistake in the code?

like image 881
eXXXXXXXXXXX2 Avatar asked Sep 27 '14 13:09

eXXXXXXXXXXX2


1 Answers

A quick examination of the assembly code generated by MSVC++ 2013 compiler shows that the delegated call from B::B(int) to B() is made incorrectly. It is a bug in the compiler.

MSVC++ constructors have a hidden boolean parameter that tells the constructor whether it is constructing a most derived object (true) or an embedded base subobject (false). In this example, only A::A() should receive true in this hidden parameter, while all lower-level constructor calls should receive false. However, when B() is called from B::B(int), the compiler unconditionally passes 1 (true) as that hidden parameter. This is incorrect.

; Code for `B::B(int)`
...
00F05223  push        1                     ; <- this is the problem
00F05225  mov         ecx,dword ptr [this]  
00F05228  call        B::B (0F010F0h)       ; <- call to `B::B()`
...

In the the properly generated code when the compiler makes a delegated constructor call, it should pass along the parameter value received from the caller, not a hardcoded 1.

The order of immediate sub-constructor calls made from A::A() in this example is as follows: 1) common virtual base D, 2) base B, 3) base D1.

Per rules of the language, in this case the constructor of B and the constructor of D1 is not supposed to construct their virtual base D. Base D is already constructed at that point by the most derived object A. This is exactly what is controlled by that hidden boolean parameter. However, when B::B() is called from B::B(int), the compiler passes an incorrect parameter value (that hardcoded 1), which caused B::B() to incorrectly assume that it constructing a most derived object. This is turn makes B re-construct the common virtual base D. This re-construction overrides the results of the proper construction already made by A::A(). Later this causes the crash.

like image 156
AnT Avatar answered Oct 24 '22 05:10

AnT