c ++ 11(工作草案)标准中的布局兼容性是否太弱?

Her*_*ert 7 c++ struct memory-layout language-lawyer c++11

当然,答案是"不",因为写它的人认为它很难,但我想知道为什么.

考虑到(无模板)类通常在头文件中声明,然后将其包含在几个单独编译的文件中,请考虑这两个文件:

在file1.c

#include <cstddef>

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

size_t getsize1(Foo const &foo) {
  return sizeof(foo);
}
Run Code Online (Sandbox Code Playgroud)

file2.c中

#include <cstddef>

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

size_t getsize2(Foo const &foo) {
  return sizeof(foo);
}
Run Code Online (Sandbox Code Playgroud)

通常,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';
}
Run Code Online (Sandbox Code Playgroud)

一种方法是使用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
Run Code Online (Sandbox Code Playgroud)

(在我的架构和环境中),显示:8,8,8.对于file1.cc,file2.cc和main.cc的每个编译,sizeof是相同的

但是c ++ 11标准是否能够保证这一点,是否真的可以期待与所有3个Foo的布局兼容?Foo包含私有和公共字段,因此它不是c ++ 11标准(工作草案)的第9章第7节中定义的标准布局结构:

标准布局类是一个类:

  • 没有非标准布局类(或此类类型的数组)或引用类型的非静态数据成员,
  • 没有虚函数(10.3),也没有虚基类(10.1),
  • 对所有非静态数据成员具有相同的访问控制(第11条),
  • 没有非标准布局基类,
  • 或者在最派生类中没有非静态数据成员,并且最多只有一个具有非静态数据成员的基类,或者没有具有非静态数据成员的基类,并且
  • 没有与第一个非静态数据成员相同类型的基类.

由于我们正在使用结构,并且要彻底,下一个标准说:

标准布局结构是使用类键结构或类键类定义的标准布局类.标准布局联合是使用类 - 键联合定义的标准布局类.

据我所知,标准只定义了标准布局中结构之间的布局兼容性(第9.2条,第18条).

如果两个标准布局结构(第9类)类型具有相同数量的非静态数据成员并且相应的非静态数据成员(按声明顺序)具有布局兼容类型(3.9),则它们是布局兼容的.

因此,保证所有三个Foo都是布局兼容的,更重要的是为什么?

为什么在编译期间为Foo创建不同布局的(非确定性)编译器不是c ++ 11编译器?

eca*_*mur 14

三者Foo是布局兼容的,因为它们是相同的类型,struct ::Foo.

[basic.types]

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

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

[基本]

9 - 在多个翻译单元中使用的名称可能会在这些翻译单元中引用同一实体,具体取决于每个翻译单元中指定名称的链接(3.5).

在命名空间范围内声明的未在未命名的命名空间中声明(递归)的类名称具有外部链接:

[basic.link]

2 - 如果一个名称可能表示与另一个范围内的声明引入的名称相同的[...]类型,则称其具有链接:
- 当名称具有外部链接时,它表示的实体可以可以通过其他翻译单位的范围或同一翻译单位的其他范围的名称来提及.[...]
4 - 未命名的命名空间或在未命名的命名空间中直接或间接声明的命名空间具有内部链接.所有其他名称空间都有外部链接.如果名称为[...]
- 命名类(第9条),或者在typedef声明中定义的未命名类,则具有未在上面给出内部链接的命名空间范围的名称具有与封闭命名空间相同的链接其中类具有用于链接目的的typedef名称(7.1.3)[...]

请注意,只要定义包含相同的标记序列,就允许在不同的翻译单元中出现多个类类型的定义:

[basic.def.odr]

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

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


一些例子:

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

// tu2.cpp
struct Foo { private: int i; public: int j; };
Run Code Online (Sandbox Code Playgroud)

这两个Foo是相同的类型.

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

// tu2.cpp
struct Foo { private: int i; public: int k; };
Run Code Online (Sandbox Code Playgroud)

ODR违规; 未定义的行为.

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

// tu2.cpp
struct Bar { private: int i; public: int j; };
Run Code Online (Sandbox Code Playgroud)

不同的名字,所以不同的类型.不兼容布局.

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

// tu2.cpp
struct Bar { int i; int j; };
Run Code Online (Sandbox Code Playgroud)

不同的名称,不同的类型,但布局兼容(自标准布局).

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

// tu2.cpp
namespace { struct Foo { private: int i; public: int j; }; }
Run Code Online (Sandbox Code Playgroud)

内部联系; 不同种类.

// 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; }; }
Run Code Online (Sandbox Code Playgroud)

没有联系; 不同种类.

// 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; }; }
Run Code Online (Sandbox Code Playgroud)

[dcl.fct.spec]/4的类型相同.

  • 因此,将`Foo`包装在一个匿名名称空间中,所有选择都关闭了吗? (2认同)
  • 绝对是@Yakk,除非`Foo`是标准布局。 (2认同)