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
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.
All compiler I know put the base class object before data members in a derived class object. Data members are in order as given in the class declaration. There might be gaps due to alignment. I'm not saying that it has to be this way though.
The order of objects in multiple inheritance is not always what you specify. From what I've experienced, the compiler will use the specified order unless it can't. It can't use the specified order when the first base class does not have virtual functions and another base class has virtual functions. In this case, the first bytes of the class has to be a virtual function table pointer, but the first base class doesn't have one. The compiler will rearrange the base classes so that the first one has a virtual function table pointer.
I've tested this with both msdev and g++ and both of them rearrange the classes. Annoyingly, they seem to have different rules for how they do it. If you have 3 or more base classes and the first one doesn't have virtual functions, these compilers will come up with different layouts.
To be safe, pick two and avoid the other.
Don't rely on the ordering of base classes when using multiple inheritance.
When using multiple inheritance, put all base classes with virtual functions before any base classes without virtual functions.
Use 2 or fewer base classes (since the compilers both rearrange in the same way in this case)
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.
The order of the members in memory is equal to the order in which they are specified in the program. Elements of non-virtual bases classes come before elements of the derived class. In the case of multiple inheritance, the elements of the first (left-most) class come first (and so on). Virtual base classes come last.
Each class/struct that is derived from a virtual base class has a pointer type prepended for its elements (theoretically implementation dependent).
The alignment of a class/struct is equal to the largest alignment of its members (theoretically implementation dependent).
Padding happens when the next element in memory needs it (for the sake of its alignment) (theoretically implementation dependent).
Trailing padding is added to make the size of an object a multiple of its alignment.
Complex example,
struct base1 {
char m_tag;
int m_base1;
base1() : m_tag(0x11), m_base1(0x1b1b1b1b) { }
};
struct derived1 : public base1 {
char m_tag;
alignas(16) int m_derived1;
derived1() : m_tag(0x21), m_derived1(0x1d1d1d1d) { }
};
struct derived2 : virtual public derived1 {
char m_tag;
int m_derived2_a;
int m_derived2_b;
derived2() : m_tag(0x31), m_derived2_a(0x2d2daa2d), m_derived2_b(0x2d2dbb2d) { }
};
struct derived3 : virtual public derived1 {
char m_tag;
int m_derived3;
virtual ~derived3() { }
derived3() : m_tag(0x41), m_derived3(0x3d3d3d3d) { }
};
struct base2 {
char m_tag;
int m_base2;
virtual ~base2() { }
base2() : m_tag(0x51), m_base2(0x2b2b2b2b) { }
};
struct derived4 : public derived2, public base2, public derived3 {
char m_tag;
int m_derived4;
derived4() : m_tag(0x61), m_derived4(0x4d4d4d4d) { }
};
Has the following memory layout:
derived4 = derived2 -> ....P....O....I....N....T....E....R....
subobject derived2 -> 0x31 padd padd padd 0x2d 0xaa 0x2d 0x2d
0x2d 0xbb 0x2d 0x2d padd padd padd padd
virual table = base2 -> ....P....O....I....N....T....E....R....
subobject base2 -> 0x51 padd padd padd 0x2b 0x2b 0x2b 0x2b
derived3 -> ....P....O....I....N....T....E....R....
subobject derived3 -> 0x41 padd padd padd 0x3d 0x3d 0x3d 0x3d
subobject derived4 -> 0x61 padd padd padd 0x4d 0x4d 0x4d 0x4d
derived1 = base1 -> 0x11 padd padd padd 0x1b 0x1b 0x1b 0x1b
subobject derived1 -> 0x21 padd padd padd padd padd padd padd
0x1d 0x1d 0x1d 0x1d padd padd padd padd
padd padd padd padd padd padd padd padd
Note that after casting a derived4 object to a derived2 or derived3, the new object starts with a pointer to the virtual base class, which is somewhere down below in the image of derived4, just like a real derived2 or derived3 object would.
Casting this derived4 to a base2 gives us an object that has a virtual table pointer, as it should (base2 has a virtual destructor).
The order of the elements is: first the (virtual base class pointer and) elements of derived2, then the (virtual table pointer and) elements of base, the (virtual base class pointer and) elements of derived3 and finally the elements of (the subobject of) derived4 -- all of that followed by the virtual base class derived1.
Also note that although a real 'derived3' object must be aligned at 16 bytes because it "contains" (at the end) the virtual base class derived1 which is aligned at 16, because it has a member with that is aligned at 16; but the 'derived3' that is used in the multiple inheritance here is NOT aligned at 16 bytes. This is OK, because the derived3 without the virtual base class has a max. alignment of just 8 (its virtual base class pointer; this is on a 64bit machine).
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.