53

Suppose I have a base class with some member variables and no virtual functions:

class Base {
   int member;
};

and a derived class that derives in a non-virtual way from Base and has no new member variables an again no virtual functions:

class Derived : Base {
};

Obviously sizeof(Derived) can't be smaller than sizeof(Base).

Is sizeof(Derived) required to be equal to sizeof(Base)?

Palec
  • 12,743
  • 8
  • 69
  • 138
sharptooth
  • 167,383
  • 100
  • 513
  • 979
  • 10
    Interesting question (+1). I don't know the answer, but don't really see why the standard would go out of its way to *require* this. – NPE Nov 13 '13 at 09:18
  • 1
    I'm not even sure that there is a guarantee in the standard that `Derived` can't be smaller than `Base`, if the empty base class rules are used. – James Kanze Nov 13 '13 at 09:34
  • @JamesKanze, that's interesting. Given `struct D : public B1, public B2 {};` with two empty base classes, the sizeof the derived type (1) is less than the sum of the sizes of the bases (1+1=2). Can we construct an example with just 1 base class? – Aaron McDaid Nov 13 '13 at 10:44
  • 1
    @AaronMcDaid In practice or in theory? In practice, I doubt it (but you can add data members to `D`, and still end up with `sizeof(D) == sizeof(B)`). But the standard doesn't prevent it; one could imagine an implementation where the size were in some way dependent on the name. (But I feel fairly confident that you'll never actually see such an implementation, and I don't worry about a loss of portability if my code doesn't take this possibility into account.) – James Kanze Nov 13 '13 at 10:52
  • In practice :-) I tested it. clang-3.3. Note, however, there are no members in either of my bases, or my derived. As per your mention of 'empty base class rules'. Hence my example might be a bit distracting from the original question. – Aaron McDaid Nov 13 '13 at 10:59
  • If `Base` is a standard-layout class, then `Derived` will be a standard-layout class as well here. Let's assume `Base` is standard-layout. Then `Base` and `Derived` are layout-compatible. This might still allow padding at the end of `Derived` (after the base class subobject), but other than that, the sizes should be equal. – dyp Nov 13 '13 at 12:23
  • 2
    Related: http://stackoverflow.com/q/19843816/420683 (which is a stricter requirement) – dyp Nov 13 '13 at 12:36
  • Just give the derive class a virtual method, you'll see the size grow by sizeof(void*). – Hans Passant Nov 13 '13 at 13:19
  • 3
    @HansPassant: I know about that and this is why I explicitly stated that in this question there're no virtual methods and no virtual inheritance. – sharptooth Nov 13 '13 at 13:28
  • Did you ever decide to rely on this? I'm tempted to inherit from a C struct so as to add constructors and member functions to structs that are defined by the OS for syscalls. The purpose would be to provide a more readable, less error-prone interface. But since it seems to not be guaranteed by the standard, perhaps use a static_assert to verify at compile time that the size is the same. Composition won't work well because sometimes the syscall requires an array of the C structs. – kec Apr 16 '19 at 23:59
  • @kec I cannot recall that. It was soooo long ago. Now it sounds like a not very good idea. – sharptooth Apr 17 '19 at 12:06

4 Answers4

19

From 5.3.2 [expr.sizeof]

When applied to a class, the result [of sizeof] is the number of bytes in an object of that class including any padding required for placing objects of that type in an array. The size of a most derived class shall be greater than zero (1.8).

From 1.8 [intro.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 sub-objects may have zero size. An object of POD type (3.9) shall occupy contiguous bytes of storage.

and a note:

The actual size of a base class subobject may be less than the result of applying sizeof to the subobject, due to virtual base classes and less strict padding requirements on base class subobjects.

Put these together and I think what it's telling you is that you have no guarantees whatsoever as to what sizeof might tell you, other than the result will be greater than zero. In fact, it doesn't even seem to guarantee that sizeof(Derived) >= sizeof(Base)!

Tristan Brindle
  • 16,281
  • 4
  • 39
  • 82
  • This is my interpretation as well. Of course, in practice, `sizeof(Derived)` will be greater than or equal `sizeof(Base)`, but the standard doesn't guarantee it. – James Kanze Nov 13 '13 at 09:36
  • @JamesKanze: I believe that the implicit conversion from `Derived&` to `Base&` provides such a guarantee. Now, it is possible that sizeof the derived class is less than the sum of the sizes of all base classes, because base subobjects can overlap, but it has to at least be equal to the size of the largest base subobject. – Ben Voigt Oct 16 '14 at 14:50
  • @BenVoigt Why? I can't think of any reason why this shouldn't hold in practice, but I can't think of any rule in the standard which would require it. Or perhaps, if `Derived` is defined when a `#pragma pack` is in effect, but `Base` no? (`#pragma pack` is, of course, not part of standard C++. But presumably, an implementation could do funny things depending on the name of the class.) – James Kanze Oct 16 '14 at 15:45
  • @JamesKanze: You have to be able to use `Derived` via `Base&`, and the only way that works is if there is a full-sized `Base` subobject for that reference to access. Subobjects are allowed to overlap; they aren't allowed to have reduced size. – Ben Voigt Oct 16 '14 at 15:54
  • @BenVoigt Sorry, but I don't follow you. Why do you need a full sized base sub-object to use `Base&` as a `Base`? All of the declared members must be there, but if there aren't any declared data members, what can you do to detect that the actual memory is less that what you expect? – James Kanze Oct 16 '14 at 18:20
13

There is no such requirement.

The only relevant part of the language I can think of is that every object, whether complete or not, and whether most-derived or not, has an identity, which is given by the pair of its address and its type. Cf. C++11 1.8/6:

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.

So both the most-derived object and the base subobject of your example must have distinct identities.

It would certainly make sense for a compiler to give both Base and Derived a size of 1, but this is not mandatory. It would be acceptable if the Base had size 1729 and Derived had size 2875.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • 2
    Hehe, I wonder if the standard imposes any constraints on non-primitive types (when it comes to their size). Maybe it could make `Base` 2875 and `Derived` 1729 just for laughs =) – luk32 Nov 13 '13 at 09:26
  • @luk32: You could do that, but the question is, what is the size of the `Base` subobject inside `Derived`. The padding can certainly differ between a complete object and a subobject of the same type. – Kerrek SB Nov 13 '13 at 09:31
  • @luk32 Such an implementation wouldn't violate the standard, but I doubt it would be widely used. Since implementers want their compiler to be used, it's usually a safe bet that they don't do such things. – James Kanze Nov 13 '13 at 09:38
  • 1
    @KerrekSB The `Base` subobject may occupy less bytes than `sizeof(Base)`. This rule was introduced because no class is allowed to have a `sizeof` of less than 1, but it was desired that empty base classes not increase the size of the object. It has some interesting consequences, however: suppose that `Base` contains only integral types, and I want to initialize it in the constructor with `memset( this, 0, sizeof(*this) )`. – James Kanze Nov 13 '13 at 09:41
  • 1
    @JamesKanze: If `Base` contains data members, surely its size cannot be zero? A base pointer points to a base object, no matter whether it's complete or not. The only difference in size can be outside what's observable from within the object. (Or did you mean that `Derived` contains only `int`s?) – Kerrek SB Nov 13 '13 at 10:02
  • 5
    @KerrekSB The "optimization" was introduced to allow empty classes to not occupy space when they are a base, and is called the empty base class optimization, but the actual wording adopted actually allows any base class to occupy a number of bytes different than what `sizeof` reports. This could actually make sense if you have `struct Base { int i; char c; }; struct Derived : Base { char c; };`; alignment considerations may mean that `sizeof(Base) == 8`, but `sizeof(Derived) == 8` as well, despite `Derived` adding a data member. – James Kanze Nov 13 '13 at 10:26
  • @JamesKanze: I see what you mean now. The fallacy is writing to `sizeof(X)` bytes of memory, rather than writing to the memory of the members of `X`. Yes, good point. – Kerrek SB Nov 13 '13 at 10:42
  • 1
    @JamesKanze: No, the base subobject cannot occupy fewer bytes than `sizeof (Base)`. It doesn't have to contribute that much to `sizeof (Derived)`, however, because subobjects of different type can overlap, as long as the actual data members all have distinct storage. – Ben Voigt Oct 16 '14 at 14:51
  • @BenVoigt Where in the standard does it say that? I can't find anything which guarantees that. (In fact, I find very little concerning the empty base class optimization at all.) – James Kanze Oct 16 '14 at 16:03
  • @JamesKanze: It isn't explicit, but follows from a combination of other rules. You can alias with a character pointer and read from the entire object, therefore widening conversions on pointers and references can't reduce the size of the object. And an upcast is a widening conversion on pointers and references. – Ben Voigt Oct 16 '14 at 16:44
  • @BenVoigt You cannot `memcpy` out from an object which is a base. At least, that's what I have been led to believe. It would be nice if someone could find sections in the standard saying exactly what you can and cannot do. (You certainly cannot `memcpy` into the base, for example.) – James Kanze Oct 16 '14 at 18:24
  • @JamesKanze: You can't `memcpy` into a base subobject, see 3.9/1 and 3.9/2. But 3.9/3 says the *object representation* of any object is `sizeof (T)` elements of `unsigned char` and in contrast to the two preceding points, it does not require a complete object or exclude base subobjects. And the strict aliasing rules say it is permitted to read that object representation directly. – Ben Voigt Oct 16 '14 at 18:49
  • oops, in last comment I meant 3.9/2,3,4, not 3.9/1,2,3 – Ben Voigt Oct 16 '14 at 18:58
  • @BenVoigt In §3.9/4, the type `T` is that of the most derived type---the "object" has type `Derived`, even if your pointer only points to `Base`. This is supported by §3.9/2, which says not only that the bytes can be copied out, but that they can be copied back in, and you end up with the same value; if the empty base class optimization is applied, it is obviously illegal to copy them back in. And in §1.8, it is clear that only "most derived objects" must occupy space. – James Kanze Oct 17 '14 at 08:17
  • @JamesKanze: I see nothing restricting the object in 3.9/4 to be a complete object. By comparison to the prior two points, that requirement is intentionally absent here. – Ben Voigt Oct 17 '14 at 12:50
  • @BenVoigt And I see nothing to suggest otherwise. Unlike a member, a base class isn't an object on its own. I think §1.8 points in this direction. (But I agree that it could be clearer.) – James Kanze Oct 17 '14 at 13:31
  • @JamesKanze: Both base subobjects and member subobjects use the same terminology, *subobject*. And the Standard makes pretty clear that both *subobjects* and *complete objects* fall into the larger category of *objects*. 1.8/2 explicitly lists *complete objects*, *member subobjects* (and array elements), and *base subobjects* all as types of object. – Ben Voigt Oct 17 '14 at 13:35
  • `So both the most-derived object and the base subobject of your example must have distinct identities.` -- does not this conflict with `may have the same address if one is a subobject of the other` in the context of the example provided? Or do I miss smth... – mlvljr Oct 30 '16 at 00:15
10

Interesting question. I have an example where a derived class with an extra field is the same size as an empty base class. (This should be a comment but is much too large; please accept one of the other answers, although upvotes are welcome if it's interesting.)

Consider this trivial C++ program:

class A {};

class B : public A {
    int m_iInteger;
};

int _tmain(int argc, _TCHAR* argv[])
{
    printf("A: %d\r\n", sizeof(A));
    printf("B: %d\r\n", sizeof(B));
    printf("int: %d\r\n", sizeof(int));

    return 0;
}

What would you expect the output to be, if sizeof(int) is 4? Perhaps something like:

A: 0
B: 4
int: 4

?

My compiler - Embarcadero C++ Builder 2010 - gives the output:

A: 8
B: 8
int: 4

In other words, adding an extra field in the derived class does not make the derived class bigger.

There is some insight into why with the help file topic on the compatibility option Zero-length empty base class.

Usually the size of a class is at least one byte, even if the class does not define any data members. When you set this option, the compiler ignores this unused byte for the memory layout and the total size of any derived classes; empty base classes do not consume space in derived classes. Default = False

It appears that the size of a class with default compiler settings for this compiler is 8 bytes, not one, and in fact changing this setting for this code example has no effect.

You may also find this article on base class sizes and the above optimization interesting. It discusses why classes must have a size of at least one byte, what the optimization does, and delves into representation of member functions etc too:

Indeed, the standard requires that the size of an object shall never be zero; it also requires that in a derived object data members of the base class(es) shall appear before user-declared data members of the derived class. However, a base class subobject isn’t considered a complete object. Therefore, it’s possible to remove the base class subobject from the derived object without violating the rules. In other words, in the object t, the offset of S and x may overlap...

Please read the article for the full context of that quote.

David
  • 13,360
  • 7
  • 66
  • 130
-1
class Base {
 // int member;            I have just created an empty base class.
};

class Derived : Base {
};

Now gcc compiler would give "size>0" for the both the objects created by Base and Derived classes. gcc compiler just provides the presence of address for the objects to the user.

  Note:Derived class would contain base class members, so obviously we can think of 
  sizeof(derived class) greater then sizeof(base class). But this depends on the compiler 
  if it allocates some extra space while defining the derived class. 

My present gcc compiler showed the sizeof objects of both Base and Derived to be same.

Santosh Sahu
  • 2,134
  • 6
  • 27
  • 51