Hi,
I am observing unexpected behaviour, in the form of a corrupted class
member access, from a simple C++ program that accesses an attribute
declared in a virtual base class via a chain of virtual method calls.
To further complicate (or perhaps simplify) matters, some compilers
(GCC and MingW) produce the expected behaviour, while others (MSVS 7.1)
do not. I can only offer two explanations for my observations:
1. The Microsoft compiler (Microsoft Visual Studio 7.1) is producing
incorrect code (unlikely).
2. My code is non-compliant (or subject to some "grey" area not covered
by the standard) causing the compilers to exhibit undefined behaviour
(more likely).
I managed to narrow the problem down to a test involving five classes
and two methods. Although my example might seem pathological, it is
based on a larger piece of code, that uses an external toolkit, which
exhibited the exact same (unexpected) behaviour. I managed to divorce
the code from the external toolkit and trimmed it down dramatically
whilst retaining the unexpected behaviour. I am unable to reduce it any
further without removing the unexpected behaviour.
The code, consisting of a single C++ source file, as well as batch
files to build it using MSVS and GCC compilers are provided below. I
built it using Microsoft Visual Studio 7.1 (C++ compiler version
13.10.3077) and GCC 3.2.3 (from MinGW).
The outputs of the programs, from the Microsoft and GCC compilers, are
shown below. It shows the values of an object's "this" pointer, as well
as the address and value of an integer attribute referenced by the
"this" pointer, within two contexts (i.e. C's and B1's), the one
context being a base class context with respect to the other one. In
the case of the code produced by the MinGW compiler, the values and
addresses are identical in both contexts (as I had hoped). However, in
the case of the code produced by the Microsoft compiler, the values of
the "this" pointers differ, leading to an incorrect attribute value
being returned. Experimentation has shown the value by which the "this"
pointer deviates from the "correct" value to be related to the size of
the most derived class (i.e. D).
Regards,
Gerhard Esterhuizen
Herewith the outputs and source and build files:
Output from MSVS compiled executable :
======================================
----------------------------------------
C::dump_addrs():
this = 0012FEC0
this_B1 = 0012FED4 // B1's "this" in C's context
&this_B1->i = 0012FED8
this_B1->i = 1234
B1::f():
this = 0012FED0 // differs from B1's "this" in C's context
&i = 0012FED4
i = 4399316
Output from MinGW compiled executable:
======================================
----------------------------------------
C::dump_addrs():
this = 0x22ff68
this_B1 = 0x22ff70 // B1's "this" in C's context
&this_B1->i = 0x22ff74
this_B1->i = 1234
B1::f():
this = 0x22ff70 // identical to B1's "this" in C's context
&i = 0x22ff74
i = 1234
Source code:
============
#include <iostream>
struct A
{
int dummy;
virtual void f() = 0;
};
struct B1 : virtual public A
{
int i;
B1() : i(1234)
{
}
virtual void f()
{
std::cerr << "B1::f():" << std::endl
<< " this = " << this << std::endl
<< " &i = " << &i << std::endl
<< " i = " << i << std::endl;
}
};
struct B2 : virtual public A
{
B2()
{
}
void g()
{
f();
}
};
struct C : virtual public B1,
public B2
{
C()
{
std::cerr << std::endl
<< "----------------------------------------" <<
std::endl;
dump_addrs();
g();
}
void dump_addrs()
{
B1* this_B1 = static_cast<B1*>(this);
std::cerr << "C::dump_addrs():" << std::endl
<< " this = " << this << std::endl
<< std::endl
<< " this_B1 = " << this_B1 << std::endl
<< " &this_B1->i = " << &this_B1->i << std::endl
<< " this_B1->i = " << this_B1->i << std::endl
<< std::endl;
}
};
struct D : public C
{
float val;
};
int main()
{
D d;
}