Why Is the Empty Base Class Optimization (Ebo) Is Not Working in Msvc

Why is the empty base class optimization (EBO) is not working in MSVC?

This is a longstanding bug in the Visual C++ compiler. When a class derives from multiple empty base classes, only the initial empty base class will be optimized using the empty base optimization (EBO).

This issue was reported on Microsoft Connect in 2006: Empty Base Optimization Not Working Properly. At the moment, old bugs are not visible on Microsoft Connect. I am told that this is a temporary issue, though I do not know when it will be resolved. In the meantime, the following is the response to the bug from Jonathan Caves, who is one of the developers on the Visual C++ compiler team:

Hi: unfortunately even though this is a bug in the Visual C++ object model we are unable to fix it at this time given that fixing it would potentially break a lot of existing programs as the sizes of objects would change. Hopefully in the future we may be able to address this issue but not for the next release of the product.

Thanks for reporting the issue.

Why is empty base optimization forbidden when the empty base class is also a member variable?

Ok, it seems as if I had it wrong all the time, since for all my examples there need to exist a vtable for the base object, which would prevent empty base optimization to start with. I will let the examples stand since I think they give some interesting examples of why unique addresses are normally a good thing to have.

Having studied this whole more in depth, there is no technical reason for empty base class optimization to be disabled when the first member is of the same type as the empty base class. This just a property of the current C++ object model.

But with C++20 there will a new attribute [[no_unique_address]] that tells the compiler that a non-static data member may not need a unique address (technically speaking it is potentially overlapping [intro.object]/7).

This implies that (emphasis mine)

The non-static data member can share the address of another non-static data member or that of a base class, [...]

hence one can "reactivate" the empty base class optimization by giving the first data member the attribute [[no_unique_address]]. I added an example here that shows how this (and all other cases I could think of) works.

Wrong examples of problems through this

Since it seems that an empty class may not have virtual methods, let me add a third example:

int stupid_method(Base *b) {
if( dynamic_cast<Foo*>(b) ) return 0;
if( dynamic_cast<Bar*>(b) ) return 1;
return 2;
}

Bar b;
stupid_method(&b); // Would expect 0
stupid_method(&b.foo); //Would expect 1

But the last two calls are the same.

Old examples (Probably don't answer the question since empty classes may not contain virtual methods, it seems)

Consider in your code above (with added virtual destructors) the following example

void delBase(Base *b) {
delete b;
}

Bar *b = new Bar;
delBase(b); // One would expect this to be absolutely fine.
delBase(&b->foo); // Whoaa, we shouldn't delete a member variable.

But how should the compiler distinguish these two cases?

And maybe a bit less contrived:

struct Base { 
virtual void hi() { std::cout << "Hello\n";}
};

struct Foo : Base {
void hi() override { std::cout << "Guten Tag\n";}
};

struct Bar : Base {
Foo foo;
};

Bar b;
b.hi() // Hello
b.foo.hi() // Guten Tag
Base *a = &b;
Base *z = &b.foo;
a->hi() // Hello
z->hi() // Guten Tag

But the last two are the same if we have empty base class optimization!

Empty Base Optimisation MSVC

Empty base optimisation with multiple inheritance appears still to be broken in msvc++ 2010

What do you mean by "broken"? That, it is not Standard conformant?

The Standard doesn't require empty classes to have zero size when deriving from them. An implementation may choose to optimize this or it may not at all.

empty base class optimization

pe10 == pe11. Can two sub-objects of
two objects have the same address? Is
this conformant?

No, two different objects cannot have same address. If they've, the compiler is not Standard Complaint.

By the way, which version of VC++ you're using? I'm using MSVC++2008, and it's output is this:

alt text

I think, you meant pe20==pe11? If so, then this also is wrong, non-standard. MSVC++2008 compiler has bug!

GCC is correct; see the output yourself : http://www.ideone.com/Cf2Ov


Similar topic:

When do programmers use Empty Base Optimization (EBO)

Struct changes size based on empty base class order

I believe MSVC conforms to the C++17 standard in this regard.

From [intro.object] (emphasis mine):

Unless it is a bit-field, a most derived object shall have a nonzero size and shall occupy one or more bytes of storage. Base class subobjects may have zero size. An object of trivially copyable or standard-layout type shall occupy contiguous bytes of storage.

That's really all the C++17 standard has to say on the matter. The empty base optimization is totally optional. The standard just says it's legal, not that it must be implemented, or in what situations it should be implemented.


The current draft of the C++20 standard is a bit more prescriptive.

From [intro.object] (emphasis again mine)

An object has nonzero size if it

-- is not a potentially-overlapping subobject, or

-- is not of class type, or

-- is of a class type with virtual member functions or virtual base classes, or

-- has subobjects of nonzero size or bit-fields of nonzero length.

Otherwise, if the object is a base class subobject of a standard-layout class type with no non-static data members, it has zero size. Otherwise, the circumstances under which the object has zero size are implementation-defined. Unless it is a bit-field, an object with nonzero size shall occupy one or more bytes of storage, including every byte that is occupied in full or in part by any of its subobjects. An object of trivially copyable or standard-layout type ([basic.types]) shall occupy contiguous bytes of storage.

So under C++20 your base class would be guaranteed to have zero size since it is an empty base class of a standard-layout class.

Empty derived optimization

The standard does not contain an "empty base class" case per se. Rather, it says (cf. 1.8):

[A] most derived object shall have a non-zero size and shall occupy one or more
bytes of storage. Base class subobjects may have zero size.

And:

Unless an object is [...] a base class subobject of zero size, the address of that object is the address of the first byte it occupies. Two objects [...] may have the same address if one is a subobject of the other, or if at least one is a base class subobject of zero size and they are of different types; otherwise, they shall have distinct addresses.

And (Clause 9):

Complete objects and member subobjects of class type shall have nonzero size. Footnote: Base class subobjects are not so constrained.

This never says that only empty bases are amenable to any kind of layout change and leaves ample room for "squeezing" the layout: for example:

struct A {}; struct B : A { int x; };             // "equivalent" to { int }

struct X { int a; }; struct Y : X {}; // "equivalent" to { int }

Consider B b; and Y y;. it is possible that the address of b, that of the A-subobject of b (i.e. &static_cast<A&>(b)) and that of b.x are the same, and similarly that the address of y, the X-suboject of y, and the address of y.a are the same.

The only thing that does not work is this:

struct S {}; struct T { S s; };                   // "equivalent" to { char }

By "equivalent" I mean the most sensible, space-saving implementation.

A more interesting case is the following:

struct Foo { int x; char a; };                    // { int, char, char[3] }

struct Bar : Foo { short q; }; // { int, char, char, short }

This example assumes sizeof(int) == 4 and sizeof(short) == 2. We have sizeof(Foo) == 8 for alignment reasons, but sizeof(Bar) is also 8 despite having more data members.


Another relevant part of the standard is 9.2/13:

Nonstatic data members of a (non-union) class with the same access control (Clause 11) are allocated so that later members have higher addresses within a class object. The order of allocation of non-static data members with different access control is unspecified (11). Implementation alignment requirements might cause two adjacent members not to be allocated immediately after each other; so might requirements for space for managing virtual functions (10.3) and virtual base classes (10.1).

Finally, 9.2/10 says that standard-layout classes have no padding at the beginning, so that their address equals the address of their "initial member". Since standard-layout requires that all bases either be empty, or that the most-derived class itself have no data members, this means that standard-layout classes must employ a kind of "empty base" optimization, and the initial part of the layout of my B and Y above is actually mandatory.

Empty Data Member Optimization: would it be possible?

It is coming to c++20 with the [[no_unique_address]] attribute.

The proposal P0840r2 has been accepted into the draft standard. It has this example:

template<typename Key, typename Value, typename Hash, typename Pred, typename Allocator>
class hash_map {
[[no_unique_address]] Hash hasher;
[[no_unique_address]] Pred pred;
[[no_unique_address]] Allocator alloc;
Bucket *buckets;
// ...
public:
// ...
};


Related Topics



Leave a reply



Submit