standard layout
本篇会简单涉及C++编译器如何在内存里排列成员变量。
标准布局指的是类的成员变量排布时,与C语言相同,按声明的顺序一个一个排。
需要符合以下条件才能做到这一点。
- 所有非静态成员有相同的访问权限(private、public、protected)
不同访问权限的变量编译器会重排。 - 派生类中有非静态成员且只有一个仅包含静态成员的基类,或者,基类有非静态成员而派生类没有非静态成员
简单地说,就是基类和派生类不能同时有非静态成员。
仔细想想也好理解,派生类会包含基类的成员,如果两边都有,排布就会不标准了。 - 类中的第一个非静态成员的类型不能是基类
这条规则很特别,是因为编译器做出的某项优化,导致本应该标准布局的情况变得不标准了,所以用这条规则来绕开这项优化。
这项优化是:如果基类没有成员,标准允许派生类的第一个成员与基类共享地址。
乍一看上去也不会影响排布,再一分析,如果派生类的第一个变量类型是基类,C++标准要求类型相同的对象必须地址不同,那么第一个变量和派生类变量就不能共享地址了,编译器会加一个字节的便宜,来强行错开派生类变量和派生类第一个成员变量。
比如以下代码,d1的类型(基类类型)和d1.b的类型相同,编译器会给d1.b强行加1字节,d2就没这个限制,B1和B2是不同的类型。
#include <iostream>
using namespace std;
struct B1 {};
struct B2 {};
struct D1 : B1 {
B1 b;
int i;
};
struct D2 : B1 {
B2 b;
int i;
};
int main() {
D1 d1;
D2 d2;
cout << hex;
cout << reinterpret_cast<long long>(&d1) << endl; // 7ffe2e9110e0
cout << reinterpret_cast<long long>(&d1.b) << endl; // 7ffe2e9110e1,多了1字节偏移
cout << reinterpret_cast<long long>(&d1.i) << endl; // 7ffe2e9110e4
cout << reinterpret_cast<long long>(&d2) << endl; // 7ffe2e9110f0
cout << reinterpret_cast<long long>(&d2.b) << endl; // 7ffe2e9110f0
cout << reinterpret_cast<long long>(&d2.i) << endl; // 7ffe2e9110f4
}
- 没有虚函数和虚基类
这一点与trivial要求相同,避免虚函数表带来的问题。 - 所有非静态数据成员均符合标准布局类型,其基类也符合标准布局
这一点没什么好说的,标准递归。
网友评论