views:

512

answers:

6

hi!

how do datamembers get aligned / ordered if inheritance / multiple inheritance is used? Is this compilerspecific?

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

Thanks!

+1  A: 

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.

Goz
Nope, only the padding between members is compiler specific. The ordering of the members is defined by the language specifications.
Thomas Matthews
Virtual table placement is "undefined". It just needs to be there. In the past there was a difference between how VC and GCC placed virtual tables in multiple inheritance cases ...
Goz
+1  A: 

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.

Autopulated
The preference is not to use compiler directives to pack the structure, but use methods to read and write members to a packed buffer. This gives a more robust program, especially when compiler vendor or versions change.
Thomas Matthews
Yes, but you have more code to maintain and introduce scope for errors if the structures change. Likelihood is your structures change more often than your compiler does ;) Of course, there are no end of serialisation/deserialiation strategies when you get started, designed to solve both the problems.
Autopulated
+3  A: 

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.

Joe Gauterin
the datastructures in question ARE POD structs (at least if multiple inheritance from other POD structs still yelds a POD struct). will then the members just be ordered something like 'basePODStruct1_members'-padding-'basePODStruct2_members'-padding-...'derivedPODStruct_members'-padding ?
genesys
@genesys: if there's any inheritance (or virtual functions, or constructors or destructors), then the structure is not POD.
Mike Seymour
The padding between members is compiler specific. The ordering of the members is defined by the language specification.
Thomas Matthews
A: 

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 ordering of members is defined by the language specification, including inheritance. Padding between members and classes during inheritance are implementation defined.
Thomas Matthews
just out of curiosity - how it comes the padding is not specified by the language?
Mat
+1  A: 

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.

doron
structs are treated no differently to classes for alignment and packing. What's important is whether the struct/class is POD or not.
Joe Gauterin
There can be an offset even with single inheritance. When a class with virtual functions derives from a POD type, popular compilers will arrange the memory as `vtableptr + POD + derived`
Shmoopty
+2  A: 

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. Generally speaking this is compiler dependant, 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:

base* base_ptr // ptr to base object
int n // derived4
int i // base

The memory layout for derived5 would be:

base* base_ptr // ptr to base object
int o // derived5
int i // base

The memory layout for derived6 would be:

base* base_ptr // ptr to base object
int n // derived4
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 necissary so that when calling any of base's functions it has an object to pass to those functions. This structure is compiler dependant 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 necissary.

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.

Chris
+1 Interesting!
DaMacc
yees - this is very very good! thanks a lot! just one more question - do those rules also hold if non-default constructors and destructors are defined?
genesys
Absolutely. Constructors and destructors have no effect on class layout unless the destructor is virtual in which case a vfptr must be present. Also, I didn't go into it because it was a bit outside the scope of the question but, be careful of the order of initialization. Initialization always occurs from low memory address to high except in the case of virtual inheritance where the virtually inherited objects are constructed first. Similarly destuctors are called from high address to low, except in the case of virtual inheritance, where the virtual inherited class's destructor is called last.
Chris
i've just read the following (in another context): "the language standard says that byte-for-byte copies are guaranteed to work only for PODs. std::pair<T,U> isn't a class aggregate, since it has a user-defined constructor, and that means it also isn't a POD." is this just a bad explanation of why one can't predict std::pair<T,U>'s memory layout or is something changing once a user defined constructor is present?
Mat
This is a complicated question but I'll try to answer it briefly.Once a user-declared (it doesn't even need to be defined) constructor is specified there is no longer a guarantee that the object has a default constructor, which means it is not a POD.That doesn't mean that it can't be copied via a memcpy, it just means that the language doesn't guarantee that the memcpy will make a true copy of the object. The reason for this (I believe) is that guaranteeing byte-for-byte copies of complex or polymorphic objects would be a royal pain and probably result in slower code execution.
Chris
do the rules also apply if a template is used? let's say base is templated and stores a value of type T instead of int (let's assume only POD types are used as template parameter) - will the int simply be replaced by a type of the templatetype or is there some additional magic going on then?
genesys
The use of templates should not alter this behavior at all. Template classes are generated at compile time and there will be a unique class generated for each combination of template parameters used.
Chris