13

Consider

struct base {};
struct child : base {};

It's well-known that sizeof(child) can be 1 by application of the empty base optimisation.

Now however, consider

struct base {};
struct child : base {base b;};

Can the compiler apply the empty base optimisation now, or must sizeof(child) be at least 2?

Reference: http://en.cppreference.com/w/cpp/language/ebo

Quentin
  • 62,093
  • 7
  • 131
  • 191
Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • 2
    As this is an optimization, what compiler and optimization flags did you use? – Quentin Jul 04 '17 at 14:52
  • But you also have a member that is of type `X`, and you cant have zero-sized members. So clearly `sizeof(X)` is 1, not 0, and there are two instances of `X`, thus `sizeof(D)` is 2. – Remy Lebeau Jul 04 '17 at 14:54
  • 1
    Here http://ideone.com/DSLOrY. The behavior is different for non-X base empty class. – Sergey Jul 04 '17 at 14:54
  • 1
    It is not. Please go by the link. – Sergey Jul 04 '17 at 14:55
  • Upvoted. Judging by the reception of my answer, this is deeper than I thought. – Bathsheba Jul 04 '17 at 15:01
  • @RaymondChen this question was asked earlier. – DAle Jul 04 '17 at 15:36
  • 3
    This is a duplicate of https://stackoverflow.com/questions/44908996/why-does-sizeofd-equal-2-in-this-code-see-details – Maxim Egorushkin Jul 04 '17 at 15:59
  • 1
    No it isn't. The focus is entirely different. Let's spend our "mod time" spotting obvious duplicates. – Bathsheba Jul 04 '17 at 16:00
  • It is exactly the same question, slightly different wording. But feel free to disagree. – Maxim Egorushkin Jul 04 '17 at 16:02
  • 2
    @Bathsheba How should the fact that you'll hit anyway the rep cap influence the fact that it's a dup or not? Not all of us are here on SO to hit a rep cap on a daily basis. This is a good question, as usually are your questions. Probably a dup, I agree, but I didn't get your last comment. I'm sorry. – skypjack Jul 04 '17 at 16:43
  • 3
    Possible duplicate of [Why does sizeof(D) equal 2 in this code (see details)?](https://stackoverflow.com/questions/44908996/why-does-sizeofd-equal-2-in-this-code-see-details) – Johan Lundberg Jul 04 '17 at 18:02
  • The linked nature of these pair of questions probably needs a moderator to sort out. I've flagged. – Bathsheba Jul 05 '17 at 07:07

4 Answers4

18

No, it cannot. From the same reference:

Empty base optimization is prohibited if one of the empty base classes is also the type or the base of the type of the first non-static data member

Thus sizeof(child) >= 2.

themiurge
  • 1,619
  • 17
  • 21
  • 4
    IMO, questions tagged [language-lawyer] should be answered with quotes from the actual spec, by its very definition. While cppreference is quite good, it is not the actual definition of how C++ works, and it has need wrong in the past. – Baum mit Augen Jul 05 '17 at 00:41
  • @Baum mit Augen thanks for pointing that out, I'm a bit new around here. It's fair to say that the other answers provided here are better than mine, then. I'll go through the relevant sections of the standard and I'll update my answer if I find something worth adding. – themiurge Jul 05 '17 at 04:55
16

The rule is that sub-objects of the same type cannot be at the same address. You have 2 X sub-objects here, hence each one must be at a different address.

Objects of the same type cannot share the same address because the identity of an object in C++ is its address. If multiples objects of the same type share the same address they are indistinguishable. And this is why the minimum complete object size is 1, so that each object in an array has a distinct address. See "§ The C++ object model [intro.object]":

An object is a region of storage.

...

Objects can contain other objects, called subobjects. A subobject can be a member subobject (9.2), a base class subobject (Clause 10), or an array element. An object that is not a subobject of any other object is called a complete object.

...

Unless it is a bit-field (9.6), 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.

...

Unless an object is a bit-field or a base class subobject of zero size, the address of that object is the address of the first byte it occupies. Two objects that are not bit-fields 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.

This is why, for example, boost::noncopyable can increase a class size if one inherits it more than once unwittingly indirectly through empty base classes. E.g. in:

struct A : boost::noncopyable {};
struct B : boost::noncopyable {};
struct C : boost::noncopyable {};
struct D : A, B, C {};

sizeof(D) == 3 because there are three distinct boost::noncopyable sub-subjects. If derivation from boost::noncopyable is dropped, then sizeof(D) == 1.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
  • I don't understand, why can't subobjects share the same address? They're empty and thus stateless, what happens when they share the same address? – Passer By Jul 04 '17 at 15:08
  • 1
    @PasserBy Sub-objects of the same type cannot share the same address because the identity of an object in C++ is its address. If multiples objects of the same type share the same address they are indistinguishable. – Maxim Egorushkin Jul 04 '17 at 15:10
  • 1
    If the size of an object could be 0 then `X x[10]; for (X *p = x[0]; p < &x[10]; ++p) {...}` would make 0 loops for example. – Goswin von Brederlow Jul 04 '17 at 15:11
  • @GoswinvonBrederlow Your example is different. It is about why minimum complete object size is 1. – Maxim Egorushkin Jul 04 '17 at 15:12
  • @GoswinvonBrederlow That is a statement that an object must not have zero size, not that some objects can't overlap – Passer By Jul 04 '17 at 15:12
  • Why does stateless objects need identity? I seriously don't see a reason for that – Passer By Jul 04 '17 at 15:13
  • Ok, not the best example. The point I was trying to make is that the address of subobjects of the same type must be unique so that comparisons (`p < &x[10`) don't give false positives. – Goswin von Brederlow Jul 04 '17 at 15:14
  • @PasserBy How else would you distinguish the stateless objects then? – Maxim Egorushkin Jul 04 '17 at 15:15
  • @PasserBy Because you can take their address and use that e.g. as key in a hashtable. – Goswin von Brederlow Jul 04 '17 at 15:15
  • @GoswinvonBrederlow But it is already illegal to compare pointers not from the same array, having the base and the member overlap here should in theory not affect what you said – Passer By Jul 04 '17 at 15:15
  • @Maxim: could you add a quote from the Standard? – Vittorio Romeo Jul 04 '17 at 15:15
  • But since they are stateless, you have no reason to store them anywhere – Passer By Jul 04 '17 at 15:15
  • @PasserBy That is not correct. It's legal to compare with equality any pointer with any other pointer of the same type. – François Andrieux Jul 04 '17 at 15:16
  • @FrançoisAndrieux I meant compare as in `<` or `>`, I thought its illegal isn't it? – Passer By Jul 04 '17 at 15:17
  • @PasserBy It's illegal to advance a pointers outside of its allocations (comparison I think is legal and verry common) but in your class D both the inherited X and the member X are in the same allocation anyway. – Goswin von Brederlow Jul 04 '17 at 15:19
  • @GoswinvonBrederlow Oh, that's news to me. But the point remains, of what use does stateless objects having identity afford? – Passer By Jul 04 '17 at 15:22
  • What use you make of it is up to you. The standard is just written so that if you use them they don't blow up. – Goswin von Brederlow Jul 04 '17 at 15:27
  • I've just tested MSVC++2015 and Intel Parallel Studio. Both compilers treat that `sizeof X` is equal to `sizeof D` and both equal to 1. Moreover, address of object of `D` is equal to the address of member `D::x`. Is it compiler bug? – Serge Goncharov Jul 04 '17 at 15:42
  • @SergeGoncharov If identity (addresses) of `X` subobjects is not examined, the compiler may optimize. Compare `&d.x == &static_cast(d)`. – Maxim Egorushkin Jul 04 '17 at 15:43
  • @Maxim: `if (&d==&d.x) puts("Bwuahaha!");` - should this give message? It gives on both compilers. – Serge Goncharov Jul 04 '17 at 15:47
  • @SergeGoncharov `printf("%d\n", &d == &d.x);` outputs 0 with clang++ and g++. – Maxim Egorushkin Jul 04 '17 at 15:50
  • @MaximEgorushkin Yes, I know that both Clang and GCC work as you say. The thing is that MS/Intel compilers think different. – Serge Goncharov Jul 04 '17 at 15:53
  • @SergeGoncharov MS C++ compilers conformance have not historically been exemplary. – Maxim Egorushkin Jul 04 '17 at 15:57
  • @MaximEgorushkin I can understand what you wrote in the [comment above](https://stackoverflow.com/questions/44909407/can-the-compiler-exploit-empty-base-optimisation-if-the-class-contains-a-member/44909645#comment76795251_44909130) but AFAICU that still doesn't answer the OP's question. Suppose, a compiler (fictitious) gives the same address to the two subobjects `base` in the question. What could go wrong with the resulting code? – João Afonso Sep 25 '20 at 20:06
14

Objects in C++ are required to have unique "identity". From [intro.object]/8 (N4659):

Two objects a and b with overlapping lifetimes that are not bit-fields may have the same address if one is nested within the other, or if at least one is a base class subobject of zero size and they are of different types; otherwise, they have distinct addresses.

A base class subobject and a member subobject are separate objects; neither is "nested within" the other. Therefore, if they are of the same type, they must have separate addresses.

Note that this extends recursively. Consider the following:

struct eb1 {};

struct eb2 : eb1 {};
struct not_empty(eb1 a;};

struct derived : eb2 {not_empty b;};

Because of the unique identity rule of C++, derived::eb2::eb1 must have a different address from derived::b::a. Therefore, the compiler cannot employ EBO on derived.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • A question going a bit deeper along that way: [Can the compiler optimize out the empty base if the class contains a member of base class type as first element, followed by other members?](https://stackoverflow.com/questions/44914678/can-the-compiler-optimize-out-the-empty-base-if-the-class-contains-a-member-of-b) – Deduplicator Jul 04 '17 at 22:32
6

I'll plop in another more basic quote

[intro.object]

Two objects a and b with overlapping lifetimes that are not bit-fields may have the same address if one is nested within the other, or if at least one is a base class subobject of zero size and they are of different types; otherwise, they have distinct addresses.


Since b is not an subobject of the inherited base, they must have distinct addresses.

Passer By
  • 19,325
  • 6
  • 49
  • 96