Do These Members Have Unspecified Ordering

Do these members have unspecified ordering?

Your colleague is correct for C++03:

[C++03: 9.2/12]: Nonstatic data members of a (non-union) class declared without an intervening access-specifier are allocated so that later members have higher addresses within a class object. The order of allocation of nonstatic data members separated by an access-specifier is unspecified (11.1). [..]

But you are correct for C++11:

[C++11: 9.2/14]: Nonstatic data members of a (non-union) class with the same access control (Clause 11) are allocated so that later members have higher addresses within a class object. The order of allocation of non-static data members with different access control is unspecified (11). [..]

(Spot the difference.)

Does public and private have any influence on the memory layout of an object?

The answer depends on the language version, because this has changed from C++03 to C++11.

In C++03, the rule was:

Members within the same access control block (that is, from one of public, protected, private keywords to the next one from that set) are to be allocated in order of declaration within class, not necessarily contiguously.

In C++11, the rule was changed to this:

Members with the same access control level (public, protected, private) are to be allocated in order of declaration within class, not necessarily contiguously.

So in C++03, you could guarantee this (I use @ to mean the offset of a member within the class):

  • @m_ac < @m_scp
  • @m_i1 < @m_i2 < @m_b1 < @m_b2

In C++11, you have a few more guarantees:

  • @m_ac < @m_scp
  • @m_sc < @m_i1 < @m_i2 < @m_b1 < @m_b2
  • @m_name < @m_b3

In both versions, the compiler can re-order the members in different chains as it sees fit, and it can even interleave the chains.


Note that there is one more mechanism which can enter into the picture: standard-layout classes.

A class is standard-layout if it has no virtuals, if all its non-static data members have the same access control, it has no base classes or non-static data members of non-standard-layout type or reference type, and if it has at most one class with any non-static data members in its inheritance chain (i.e. it cannot both define its own non-static data members and inherit some from a base class).

If a class is standard-layout, there is an additional guarantee that the address of its first non-static data member is identical to that of the class object itself (which just means that padding cannot be present at the beginning of the class layout).

Note that the conditions on being standard-layout, along with practical compilers not making pessimising choices, effectively mean that in a standard-layout class, members will be arranged contiguously in order of declaration (with padding for alignment interspersed as necessary).

Struct Reordering by compiler

Why is the compiler forbidden (by the standard) from reordering the struct?

The basic reason is: for compatibility with C.

Remember that C is, originally, a high-level assembly language. It is quite common in C to view memory (network packets, ...) by reinterpreting the bytes as a specific struct.

This has led to multiple features relying on this property:

  • C guaranteed that the address of a struct and the address of its first data member are one and the same, so C++ does too (in the absence of virtual inheritance/methods).

  • C guaranteed that if you have two struct A and B and both start with a data member char followed by a data member int (and whatever after), then when you put them in a union you can write the B member and read the char and int through its A member, so C++ does too: Standard Layout.

The latter is extremely broad, and completely prevents any re-ordering of data members for most struct (or class).


Note that the Standard does allow some re-ordering: since C did not have the concept of access control, C++ specifies that the relative order of two data members with a different access control specifier is unspecified.

As far as I know, no compiler attempts to take advantage of it; but they could in theory.

Outside of C++, languages such as Rust allow compilers to re-order fields and the main Rust compiler (rustc) does so by default. Only historical decisions and a strong desire for backward compatibility prevent C++ from doing so.

Why does the C++ standard specifically grant leeway regarding memory layout of class data members with different access specifiers?

N2062 is the first C++ paper that deals with changes to C++98/03's POD definition. It was written as a means to resolve core issue 568, which is about PODs and type layouts. It represents the beginning of the design that leads to C++11's standard layout and trivial copyability definitions.

And yet, N2062 never even considers defining the layout of members with different access controls. It doesn't even give justification for why this restriction is in place. Nor does the final version of that proposal, which actually gives us trivially-copyable and standard-layout definitions. All versions of these proposals take the access control limitation as an fait accompli, rather than something that could have been changed.

All this suggests that the writer of the proposal had knowledge of at least one compiler/ABI that changes the order of members based on access controls.

Order of In Class Initialization versus Constructor Initialization List

This is guaranteed by the standard that non-static data members will be initialized in the order of their declarations in the class definition. How they're initialized (via default member initializer or member initializer list) and the order of these initializers don't matter.

[class.base.init]#13.3

(13.3) - Then, non-static data members are initialized in the order they were declared in the class definition (again regardless of the order of the mem-initializers).

[ Note: The declaration order is mandated to ensure that base and member subobjects are destroyed in the reverse order of initialization. — end note ]

That means, the initialization order will always be ptr -> m1 -> m2.

Class layout in C++: Why are members sometimes ordered?

This freedom makes it impossible in theory to link binaries created by different compilers.

It's impossible for a number of reasons, and structure layout is the most minor. vtables, implementations of operator new and delete, data type sizes...

So what are the remaining reasons for the strict in-section ordering?

C compatibility, I would have thought, so that a struct defined in C packs the same way it does in C++ for a given compiler set.

And does the new C++0911 standard provide a way to fully determine object layouts "by hand"?

No, no more than the current standard does.

For a class or struct with no vtable and entirely private (or public) fields, though, it's already possible if you use the [u]int[8|16|32|64]_t types. What use case do you have for more than this?



Related Topics



Leave a reply



Submit