C++11标准中的布局兼容性是否太弱?

7
当然,答案是“不”,因为写这个代码的人很认真地考虑过了,但我想知道为什么。
考虑到(没有模板的)类经常在头文件中声明,然后被包含在编译成多个文件的头文件中,进一步考虑这两个文件:

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);
}

通常情况下,Foo会在头文件中声明并被包含在两个文件中,但效果如上所示。(也就是说,包含头文件并不是什么魔法,它只是把头文件的内容放在那一行上。)我们可以编译这两个文件并将它们链接到以下文件:

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';
}

其中一种方法是使用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

在我的架构和环境下,这显示为: 8, 8, 8。对于file1.cc、file2.cc和main.cc的每次编译,sizeof都是相同的。但是,c++11标准是否保证了这一点?期望所有3个Foo具有布局兼容性真的可以吗?Foo包含私有和公共字段,因此它不是标准布局结构,如c++11标准(工作草案)第9段第7款所定义的那样:
“标准布局类是指一个类: - 没有非静态数据成员是非标准布局类的类型(或该类型的数组)或引用类型, - 没有虚函数(10.3)和没有虚基类(10.1), - 所有非静态数据成员具有相同的访问控制(第11条), - 没有非标准布局的基类, - 要么在最派生类中没有非静态数据成员,在最多一个带有非静态数据成员的基类中只有一个非静态数据成员,要么没有带有非静态数据成员的基类 - 第一个非静态数据成员没有与其类型相同的基类。”
由于我们使用的是structs,并且为了详尽起见,下一段说:
“标准布局结构体是使用class-key struct或class-key class定义的标准布局类。标准布局联合是使用class-key union定义的标准布局类。”
据我所知,标准仅在标准布局结构体之间定义了布局兼容性(第9.2段,第18款)。
“如果它们具有相同数量的非静态数据成员,并且相应的非静态数据成员(按声明顺序)具有布局兼容的类型(3.9),则两个标准布局结构体(第9条)类型具有布局兼容性。”
因此,是否保证所有三个Foo具有布局兼容性,更重要的是为什么?
一个(非确定性的)编译器为什么会在编译期间为Foo创建不同的布局,而又不是c++11编译器?

2
我不能给出权威或确定的答案,但我会说是的,预计这三个不同(但相等)的类应该具有相同的布局,否则将它们放在头文件中将无法工作(正如你所注意到的)。 - Some programmer dude
8
这与“布局兼容”的概念完全无关,而与“一个定义规则”有关。 Foo 的所有三个定义都指的是同一个实体,在所有翻译单元中必须具有相同的属性集。为此,所有定义必须在标记上完全相同,并且这些标记必须具有相同的含义;否则,程序将是不合法的,无需进行任何诊断。有关详细信息,请参见 3.2/5 - Igor Tandetnik
@Cameron:我知道这种非确定性编译器存在的问题,我只是想知道标准是如何描述这种确定性行为的。 - Herbert
1
如果这两个类有不同的名称,编译器是否允许对它们进行不同的编译?是的(除非它们是标准布局类型)。 - n. m.
当然,答案是否定的,因为编写它的人非常认真考虑过。我认为这个论点是无意义的。如果是真的,那么以前的标准措辞就永远不会被改变。 - Lightness Races in Orbit
显示剩余9条评论
1个回答

14

由于它们是相同类型的struct ::Foo,所以这三个Foo是布局兼容的。

[basic.types]

11 - 如果两种类型T1和T2是相同的类型,则T1和T2是布局兼容的类型。

这些类是相同类型的,因为它们具有相同的(完全限定的)名称并具有外部链接:

[basic]

9 - 在多个翻译单元中使用的名称可以根据每个翻译单元中指定的名称的链接(3.5)可能引用这些翻译单元中的相同实体。

在未命名名称空间内没有递归声明的类名称具有外部链接:

[basic.link]

2 - 当一个名称可能表示与在另一个作用域中通过声明引入的名称相同的[...]类型时,称该名称具有链接:
— 当名称具有外部链接时,它所表示的实体可以从其他翻译单元或同一翻译单元的其他作用域的名称引用。[...]
4-未命名名称空间或直接或间接在未命名名称空间内声明的名称空间具有内部链接。所有其他名称空间具有外部链接。如果未给具有命名类(第9条)或在typedef声明中定义了无名称类且该类为链接目的而具有typedef名称的名称的命名空间范围的名称上述内部链接,则其具有与封闭命名空间相同的链接[...]

请注意,允许在不同翻译单位中有类类型的多个定义,只要这些定义由相同的标记序列组成:

[basic.def.odr]

6 - 在程序中可以有一个类类型(第9条)的多个定义[...],前提是每个定义出现在不同的翻译单元中,并且前提是[...]每个定义[...]应由相同的令牌序列组成[...]

因此,如果Foo具有不同的名称,则它们不是相同的类型;如果它们出现在匿名名称空间或函数定义中(除了内联函数;请参见[dcl.fct.spec]/4),则它们将没有外部链接,因此它们不是相同的类型。在任一情况下,只有它们是标准布局时,它们才是布局兼容的。


一些例子:

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

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

这两个 Foo 是同一类型。

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

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

ODR违例;未定义行为。

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

// tu2.cpp
struct Bar { private: int i; public: int j; };
不同的名称代表不同的类型,因此它们的布局不兼容。
// tu1.cpp
struct Foo { int i; int j; };

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

不同的名称,不同的类型,但布局兼容(因为标准布局)。

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

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

内部链接;不同类型。

// 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; }; }

没有联系;不同类型。

// 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; }; }

同类型参见 [dcl.fct.spec]/4。


1
你能否补充说明,两个类必须具有相同的名称才能被视为同一类型?(请参见n.m.的评论) - Herbert
2
那么,将 Foo 包装在匿名命名空间中,所有的赌注都取消了? - Yakk - Adam Nevraumont
2
@Yakk 绝对没错,除非 Foo 是标准布局。 - ecatmur

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接