Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is layout-compatibility in the c++11 (working draft) standard too weak?

Of course, the answer is "no", because the people who wrote it thought really hard about it, however I want to know why.

Considering that (template-less) classes are often declared in header files, which are then included in several files which are compiled separately, sub-consider these two files:

file1.c

#include <cstddef>

struct Foo {
public:
   int pub;
private:
   int priv;
};

size_t getsize1(Foo const &foo) {
  return sizeof(foo);
}

file2.c

#include <cstddef>

struct Foo {
public:
   int pub;
private:
   int priv;
};

size_t getsize2(Foo const &foo) {
  return sizeof(foo);
}

In general Foo will be declared in a header file and included in both, but the effect is as shown above. (That is, including a header is no magic, it just puts the headers content on that line.) We can compile both and link them to the following:

main.cc

#include <iostream>
struct Foo {
public:
   int pub;
private:
   int priv;
};

size_t getsize1(Foo const &);
size_t getsize2(Foo const &);

int main() {
    Foo foo;
    std::cout << getsize1(foo) << ", " << getsize2(foo) << ", " << sizeof(foo) << '\n';
}

One way to do so, is by using g++:

g++ -std=c++11 -c -Wall file1.cc 
g++ -std=c++11 -c -Wall file2.cc 
g++ -std=c++11 -c -Wall main.cc 
g++ -std=c++11 -Wall *.o -o main

And (on my architecture and environment), this shows: 8, 8, 8. The sizeof's are the same for each compilation of file1.cc, file2.cc and main.cc

But does the c++11 standard guarantee this, is it really OK to expect to have layout compatibility with all 3 Foo's? Foo contains both private and public fields, hence it is not a standard-layout struct as is defined in Clause 9 par 7 of the c++11 standard (working draft):

A standard-layout class is a class that:

  • has no non-static data members of type non-standard-layout class (or array of such types) or reference,
  • has no virtual functions (10.3) and no virtual base classes (10.1),
  • has the same access control (Clause 11) for all non-static data members,
  • has no non-standard-layout base classes,
  • either has no non-static data members in the most derived class and at most one base class with non-static data members, or has no base classes with non-static data members, and
  • has no base classes of the same type as the first non-static data member.

Since we are using structs, and to be thorough, the next par says:

A standard-layout struct is a standard-layout class defined with the class-key struct or the class-key class. A standard-layout union is a standard-layout class defined with the class-key union.

To the best of my knowledge, the standard only defines layout-compatibility between structs in standard layout (Clause 9.2, par 18).

Two standard-layout struct (Clause 9) types are layout-compatible if they have the same number of non-static data members and corresponding non-static data members (in declaration order) have layout compatible types (3.9).

So is it guaranteed all three Foo's are layout-compatible, and more importantly why?

Why would a (non-deterministic) compiler, which creates different layouts for Foo during compilation, not be a c++11 compiler?

like image 609
Herbert Avatar asked Oct 23 '14 13:10

Herbert


1 Answers

The three Foos are layout-compatible because they are the same type, struct ::Foo.

[basic.types]

11 - If two types T1 and T2 are the same type, then T1 and T2 are layout-compatible types.

The classes are the same type because they have the same (fully-qualified) name and have external linkage:

[basic]

9 - A name used in more than one translation unit can potentially refer to the same entity in these translation units depending on the linkage (3.5) of the name specified in each translation unit.

Class names declared at namespace scope that are not declared (recursively) within an unnamed namespace have external linkage:

[basic.link]

2 - A name is said to have linkage when it might denote the same [...] type [...] as a name introduced by a declaration in another scope:
— When a name has external linkage , the entity it denotes can be referred to by names from scopes of other translation units or from other scopes of the same translation unit. [...]
4 - An unnamed namespace or a namespace declared directly or indirectly within an unnamed namespace has internal linkage. All other namespaces have external linkage. A name having namespace scope that has not been given internal linkage above has the same linkage as the enclosing namespace if it is the name of [...]
— a named class (Clause 9), or an unnamed class defined in a typedef declaration in which the class has the typedef name for linkage purposes (7.1.3) [...]

Note that it is allowed to have multiple definitions of a class type appearing in different translation units, as long as the definitions consist of the same token sequence:

[basic.def.odr]

6 - There can be more than one definition of a class type (Clause 9) [...] in a program provided that each definition appears in a different translation unit, and provided [...] each definition [...] shall consist of the same sequence of tokens [...]

So if the Foos had different names, they would not be the same type; if they appeared within an anonymous namespace or within a function definition (except an inline function; see [dcl.fct.spec]/4) they would not have external linkage and so would not be the same type. In either case they would be layout-compatible only if they were standard-layout.


Some examples:

// tu1.cpp
struct Foo { private: int i; public: int j; };

// tu2.cpp
struct Foo { private: int i; public: int j; };

The two Foos are the same type.

// tu1.cpp
struct Foo { private: int i; public: int j; };

// tu2.cpp
struct Foo { private: int i; public: int k; };

ODR violation; undefined behavior.

// tu1.cpp
struct Foo { private: int i; public: int j; };

// tu2.cpp
struct Bar { private: int i; public: int j; };

Different names, so different types. Not layout-compatible.

// tu1.cpp
struct Foo { int i; int j; };

// tu2.cpp
struct Bar { int i; int j; };

Different names, different types, but layout-compatible (since standard-layout).

// tu1.cpp
namespace { struct Foo { private: int i; public: int j; }; }

// tu2.cpp
namespace { struct Foo { private: int i; public: int j; }; }

Internal linkage; different types.

// tu1.cpp
static void f() { struct Foo { private: int i; public: int j; }; }

// tu2.cpp
static void f() { struct Foo { private: int i; public: int j; }; }

No linkage; different types.

// tu1.cpp
inline void f() { struct Foo { private: int i; public: int j; }; }

// tu2.cpp
inline void f() { struct Foo { private: int i; public: int j; }; }

Same type by [dcl.fct.spec]/4.

like image 89
ecatmur Avatar answered Sep 22 '22 16:09

ecatmur