40

From the code below sizeof(Base) == 24 and sizeof(Derived) == 24.

Why are their sizes equal?

In Base class we have 3 members and in Derived class we have another member.

class Base
{
private:
    double d;
protected:
    long l;
public:
    int i;
};

class Derived : public Base
{
private:
    float f;
};
leemes
  • 42,229
  • 18
  • 115
  • 172
Axazeano
  • 890
  • 9
  • 22

4 Answers4

51

It just so happened that your class Base has 8 byte alignment requirement, but its last member has size 4. This leads to an empty padding area added at the end of Base's memory layout. That extra padding plays its role when you instantiate objects of class Base by themselves, as so called most-derived objects.

Base b; // <- a most-derived object
Base a[10]; // <- an array of most-derived objects

However, when you "embed" Base as base class into class Derived, there's no need for that extra padding at the end of the embedded Base subobject.

Derived d; // <- object `d` contains an embedded sub-object of type `Base`

A smart compiler will attempt to reuse that area by placing the extra field of class Derived into the layout area used for padding in Base. In your case the extra field Derived::f incidentally has the same size of 4 bytes, i.e. it fits in there perfectly. The end result is that the total size of the class does not increase.

A very similar (in nature) effect is so called "empty base optimization". In C++ sizeof for any type is guaranteed to be greater than 0, which means that sizeof of an empty class is always greater than zero. However, when you derive some other class from an empty base class, you might observe that the base class contributes exactly 0 bytes to the derived class's size. For example

struct A {};
struct B {};
struct C {};
struct D {};

struct F : A, B, C, D {
  int i;
}

int main() {
  std::cout << sizeof(A) << std::endl << sizeof(B) << std::endl << 
               sizeof(C) << std::endl << sizeof(D) << std::endl;
  std::cout << sizeof(F) << std::endl;
}

Even though sizeof of each base class is greater than zero, sizeof(F) will typically still evaluate to sizeof(int), as if base class subobjects do not exist at all.

In other words, as such examples show, base class subobjects follow noticeably more relaxed rules with regard to their memory layout than most-derived objects. These relaxed rules might easily lead to situations when sizeof of base class will only partially contribute to sizeof of derived class.

AnT
  • 291,388
  • 39
  • 487
  • 734
  • 1
    Note that the decision to reuse the area is done in the ABI, compilers usually don't have much choice about it. – Marc Glisse Jan 19 '14 at 19:26
  • 3
    @Marc Glisse: ABI is a different story from a different realm. Who said there even is an ABI? As far as language itself is concerned, there's no such thing as ABI. A more relevant remark might be that such matters can actually be dictated to the compiler by the user, who can manipulate different implementation-dependent compiler facilities that allow one to override default alignment/packing rules. – AnT Jan 19 '14 at 22:54
  • 2
    how is the alignment requirement calculated i.e on what basis? is it on the data bus size? – Koushik Shetty Jan 20 '14 at 03:20
  • @Koushik: There are many factors involved here. A typical situation with most hardware is that a fundamental type of size N should be aligned at the N-th byte boundary. It is either a hardware requirement (the code will crash if the requirement is violated) or a performance gaudeline (the code will perform worse if the requirement is violated). On top of that you have your own higher-level considerations: cache performance issues might make you to prefer a greater alignment value, memory consumption issues might make you to prefer a smaller alignment value (if available). And so on. – AnT Jan 20 '14 at 19:22
9

Because you have sizeof(double) == sizeof(long) == 8 and it often means alignof(double) is equal to 8 too. It means that Base must be size aligned on an 8 bytes boundary in case it is store in an array, and it generates a 4 bytes padding at the end, Derived removes that to put f instead.

galop1n
  • 8,042
  • 17
  • 34
5

Use pahole to figure it out:

class Base {
private:

    double                     d;                    /*     0     8 */
protected:

    long int                   l;                    /*     8     8 */
    int                        i;                    /*    16     4 */


    /* size: 24, cachelines: 1, members: 3 */
    /* padding: 4 */
    /* last cacheline: 24 bytes */
};
class Derived : public Base {
public:

    /* class Base                <ancestor>; */      /*     0    24 */

    /* XXX last struct has 4 bytes of padding */
private:

    /* Bitfield combined with next fields */

    float                      f;                    /*    20     4 */


    /* size: 24, cachelines: 1, members: 2 */
    /* paddings: 1, sum paddings: 4 */
    /* last cacheline: 24 bytes */
};
peppe
  • 19,728
  • 3
  • 47
  • 65
2

Padding due to alignment needs:

Although the compiler (or interpreter) normally allocates individual data items on aligned boundaries, data structures often have members with different alignment requirements. To maintain proper alignment the translator normally inserts additional unnamed data members so that each member is properly aligned. In addition the data structure as a whole may be padded with a final unnamed member. This allows each member of an array of structures to be properly aligned.

More on this here:
http://en.wikipedia.org/wiki/Data_structure_alignment#Data_structure_padding

Salman A
  • 229,425
  • 77
  • 398
  • 489
Adam D. Ruppe
  • 24,584
  • 4
  • 36
  • 58