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?
Thanks!
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.