Unexpected behaviour with virtual base classes

  • Thread starter Gerhard Esterhuizen
  • Start date
G

Gerhard Esterhuizen

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;
}
 
G

Greg

Gerhard said:
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).

It certainly cannot be ruled out. The older versions of the Microsoft
compilers had a surprising number of problems. The more recent VS C++
compilers seem to have fixed many of at least the most serious ones.
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 ....
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();
}
};

The implementation of B2::g() certainly looks like it has a problem.
Where is the implementation of f() that will be called from g()? The
only apparent possibility is A::f(), but A::f() has not been defined. I
would test whether defining A::f() resolves the problem.

Admittedly, it's odd that the compiler would not complain, but perhaps
virtual inheritance somehow prevents an error from being reported.
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;
}
};

I don't really see what useful conclusion can be drawn from outputing
these pointer values. A pointer to an instance of a polymorphic type,
especially one with virtual inheritance, can and will vary depending on
the class scope in which it appears. Therefore two pointers that do not
reference the same address, may still compare as equal. The only way to
know whether the pointers are equal is to have the program test them
for equality.

Otherwise, the interpretation of these pointer values is
implementation-dependent and as such few meaningful conclusions can be
drawn from the values alone. In other words, since we have no way of
knowing what the pointer values should be, we have no way of telling
that the pointer values that we do observe, are anything other than
what they should be.

Greg
 
G

Gerhard Esterhuizen

Hi Greg,

Thanks for the reply.

At this point any further debate seems fairly academic, as Albert
Strasheim pointed out to me that this is a known bug. Details at


http://lab.msdn.microsoft.com/produ...edbackid=2cf13d49-af0f-49bc-baa1-403ebf5085c8

Nontheless, I would still like to comment on your comments:

I don't see anything wrong with B2::g() calling f(): as f() is declared
virtual in a A, which is a base of B2, the implementation can reside in
any class derived from A (and does reside in B1, which is derived from
A). That's polymorhism at work.

As for your concerns about the usefulness of the pointer dumps: I agree
that you won't find any use comparing GCC's pointer values with MSVS's
(for the reasons you specified). However, for the output of each
compiler, I quote *two* sets of pointers and *two* accesses to the same
member (B1::i). The only difference is the contexts within which these
values are obtained (i.e. in the one case I am in a method of C, which
is derived from B1, and in the other case I am in a method of B1). In
the above example, the MSVS compiler shows B1::i's value to be either
1234 or 4399316, depending on the context, while GCC shows it as 1234
in both contexts (the expected behaviour). The different values shown
by the MS compiler is indicative of the problem (if I were to write to
B1::i from B1's context, I would surely corrupt some memory). I
apologise for not being clear enough about this in the original post.

Regards,

Gerhard
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,070
Latest member
BiogenixGummies

Latest Threads

Top