c++ data alignment /member order & inheritance

16,740

Solution 1

Really you’re asking a lot of different questions here, so I’m going to do my best to answer each one in turn.

First you want to know how data members are aligned. Member alignment is compiler defined, but because of how CPUs deal with misaligned data, they all tend to follow the same

guideline that structures should be aligned based on the most restrictive member (which is usually, but not always, the largest intrinsic type), and strucutres are always aligned such that elements of an array are all aligned the same.

For example:

struct some_object
{
    char c;
    double d;
    int i;
};

This struct would be 24 bytes. Because the class contains a double it will be 8 byte aligned, meaning the char will be padded by 7 bytes, and the int will be padded by 4 to ensure that in an array of some_object, all elements would be 8 byte aligned (the size of an object is always a multiple of its alignment). Generally speaking this is compiler dependent, although you will find that for a given processor architecture, most compilers align data the same.

The second thing you mention is derived class members. Ordering and alignment of derived classes is kinda a pain. Classes individually follow the rules I described above for structs, but when you start talking about inheritance you get into messy turf. Given the following classes:

class base
{
    int i;
};

class derived : public base // same for private inheritance
{
    int k;
};

class derived2 : public derived
{
    int l;
};

class derived3 : public derived, public derived2
{
    int m;
};

class derived4 : public virtual base
{
    int n;
};

class derived5 : public virtual base
{
    int o;
};

class derived6 : public derived4, public derived5
{
    int p;
};

The memory layout for base would be:

int i // base

The memory layout for derived would be:

int i // base
int k // derived

The memory layout for derived2 would be:

int i // base
int k // derived
int l // derived2

The memory layout for derived3 would be:

int i // base
int k // derived
int i // base
int k // derived
int l // derived2
int m // derived3

You may note that base and derived each appear twice here. That is the wonder of multiple inheritance.

To get around that we have virtual inheritance.

The memory layout for derived4 would be:

void* base_ptr // implementation defined ptr that allows to find base
int n // derived4
int i // base

The memory layout for derived5 would be:

void* base_ptr // implementation defined ptr that allows to find base
int o // derived5
int i // base

The memory layout for derived6 would be:

void* base_ptr // implementation defined ptr that allows to find base
int n // derived4
void* base_ptr2 // implementation defined ptr that allows to find base
int o // derived5
int i // base

You will note that derived 4, 5, and 6 all have a pointer to the base object. This is necessary so that when calling any of base's functions it has an object to pass to those functions. This structure is compiler dependent because it isn't specified in the language spec, but almost all compilers implement it the same.

Things get more complicated when you start talking about virtual functions, but again, most compilers implement them the same as well. Take the following classes:

class vbase
{
    virtual void foo() {}
};

class vbase2
{
    virtual void bar() {}
};

class vderived : public vbase
{
    virtual void bar() {}
    virtual void bar2() {}
};

class vderived2 : public vbase, public vbase2
{
};

Each of these classes contains at least one virtual function.

The memory layout for vbase would be:

void* vfptr // vbase

The memory layout for vbase2 would be:

void* vfptr // vbase2

The memory layout for vderived would be:

void* vfptr // vderived

The memory layout for vderived2 would be:

void* vfptr // vbase
void* vfptr // vbase2

There are a lot of things people don't understand about how vftables work. The first thing to understand is that classes only store pointers to vftables, not whole vftables.

What that means is that no matter how many virtual functions a class has, it will only have one vftable, unless it inherits a vftable from somewhere else via multiple inheritance. Pretty much all compilers put the vftable pointer before the rest of the members of the class. That means that you may have some padding between the vftable pointer and the class's members.

I can also tell you that almost all compilers implement the pragma pack capabilities which allow you to manually force structure alignment. Generally you don't want to do that unless you really know what you are doing, but it is there, and sometimes it is necessary.

The last thing you asked is if you can control ordering. You always control ordering. The compiler will always order things in the order you write them in. I hope this long-winded explanation hits everything you need to know.

Solution 2

It's not just compiler specific - it's likely to be affected by compiler options. I'm not aware of any compilers that give you fine grained control over how members and bases are packed and ordered with multiple inheritance.

If you're doing something that relies on order and packing, try storing a POD struct inside your class and using that.

Solution 3

It is compiler specific.

Edit: basically it comes down to where the virtual table is placed and that can be different depending on which compiler is used.

Solution 4

As soon as your class is not POD (Plain old data) all bets are off. There are probably compiler-specific directives you can use to pack / align data.

Solution 5

Compilers generally align data members in structs to allow for easy access. This means that data elements will normally start on word boundaries and it gaps will normally be left in a struct to ensure that word boundaries are not straddled.

so

struct foo
{
    char a;
    int b;
    char c;
}

Will normally take up more than 6 bytes for a 32 bit machine

The base class is normally layed out first and the derived class it layed out after the base class. This allows the address of the base class to equal the address of the derived class.

In multiple inheritance there is an offset between the address of a class and the address of the second base class. >static_cast and dynamic_cast will calculate the offset. reinterpret_cast does not. C style casts do a static cast if possible otherwise a reinterpret cast.

As others have mentioned, all this is compiler specific but the above should give you a rough guide of what normally happens.

Share:
16,740
genesys
Author by

genesys

Updated on June 07, 2022

Comments

  • genesys
    genesys about 2 years

    How do data members get aligned / ordered if inheritance / multiple inheritance is used? Is this compiler specific?

    Is there a way to specify in a derived class how the members (including the members from the base class) shall be ordered / aligned?