为什么将此 POD 结构用作基类会很危险
Why can it be dangerous to use this POD struct as a base class?
我和一位同事进行了这次对话,结果很有趣。假设我们有以下 POD 类
struct A {
void clear() { memset(this, 0, sizeof(A)); }
int age;
char type;
};
clear
旨在清除所有成员,设置为0
(按字节(。如果我们使用 A
作为基类,可能会出错?这里有一个微妙的错误来源。
编译器可能会向 A 添加填充字节。因此,sizeof(A)
超出了char type
(直到填充的末尾(。但是,在继承的情况下,编译器可能不会添加填充的字节。因此,对 memset
的调用将覆盖子类的一部分。
除了其他注释之外,sizeof
是一个编译时运算符,因此clear()
不会将派生类添加的任何成员清零(除非由于填充奇怪而注明(。
这没有什么真正"微妙"的; memset
在C++中使用是一件可怕的事情。在极少数情况下,你真的可以用零填充内存并期望理智的行为,并且你真的需要用零填充内存,并且通过初始化项列表以文明的方式零初始化所有内容在某种程度上是不可接受的,请使用std::fill
代替。
理论上,编译器可以以不同的方式布置基类。 C++03 §10 第 5 段说:
基类子对象的布局 (3.7( 可能与相同类型的大多数派生对象的布局不同。
正如 StackedCrooked 所提到的,当基类作为自己的对象存在时,编译器可能会在基类的末尾添加填充A
发生这种情况,但编译器可能不会在基类时添加该填充。 这将导致A::clear()
覆盖子类成员的前几个字节。
然而,在实践中,我无法在GCC或Visual Studio 2008中做到这一点。 使用此测试:
struct A
{
void clear() { memset(this, 0, sizeof(A)); }
int age;
char type;
};
struct B : public A
{
char x;
};
int main(void)
{
B b;
printf("%d %d %dn", sizeof(A), sizeof(B), ((char*)&b.x - (char*)&b));
b.x = 3;
b.clear();
printf("%dn", b.x);
return 0;
}
并且修改A
、B
或两者以"打包"(VS 中#pragma pack
,GCC 中__attribute__((packed))
(,无论如何我都无法覆盖b.x
。 已启用优化。 为尺寸/偏移打印的 3 个值始终为 8/12/8、8/9/8 或 5/6/5。
类的 clear
方法将仅设置类成员的值。
根据对齐规则,允许编译器插入填充,以便下一个数据成员将出现在对齐的边界上。 因此,在type
数据成员之后会有填充。 后代的第一个数据成员将占据这个插槽并且不受memset
的影响,因为基类sizeof
不包括后代的大小。 父级的大小 != 子级的大小(除非子级没有数据成员(。 请参阅切片。
结构包装不是语言标准的一部分。 希望有一个好的编译器,打包结构的大小不会在最后一个字节之后包含任何额外的字节。 即便如此,从打包父级继承的打包后代也应产生相同的结果:parent 仅设置父级中的数据成员。
简而言之:在我看来,唯一的一个潜在问题是我在 C89、C2003 标准中找不到有关"填充字节"保证的任何信息......它们是否有一些非凡的易失性或只读行为 - 我什至找不到标准术语"填充字节"是什么意思......
详细:
对于 POD 类型的对象,C++2003 标准保证:
- 当您将对象
- 的内容内存为字符数组或无符号字符数组,然后将内容内存回对象时,该对象将保留其原始值
保证 POD 对象的开头不会有填充
可以违反有关以下内容C++规则:转到语句,生存期
对于C89,还存在一些关于结构的保证:
-
当用于联合结构的混合时,如果结构具有相同的开头,则第一个组合具有完美的数学
-
sizeof结构在C中等于存储所有组件的内存量,组件之间的填充位置,将填充放在以下结构下
-
在 C 语言中,结构的组件被赋予地址。可以保证地址的组成部分按升序排列。并且第一个组件的地址与结构的起始地址一致。无论运行程序的计算机是哪个字节序
所以在我看来,这样的规则也适合C++,一切都很好。我真的认为在硬件级别没有人会限制您为非常量对象写入填充字节。
- std::具有相同基类的类的变体
- 是否可以初始化不可复制类型的成员变量(或基类)
- 在C++中,是否可以基于给定的标识符创建基类的新实例,反之亦然
- 基类中的函数名称解析
- C++初始化基类
- 如何通过派生类函数更改基类中的向量
- 如何定义一个纯抽象基类
- 如何使用基类指针引用派生类成员
- 继承:构造函数,初始化C++11中基类的类C数组成员
- 使用基类指针创建对象时,缺少派生类析构函数
- 如何引用基类的派生类?
- 如果基类包含双指针成员,则派生类的构造函数
- 在模板基类中为继承类中的可选重写生成虚拟方法
- 为什么此派生对象无法访问基类的后递减方法?
- 公开最直接的基类模板名称
- 当基类是依赖类型时,这是一个缺陷吗
- 如何使基类的运算符对基类的可变参数数可见(请参阅下面的代码)?
- 模板基类中的静态变量
- C++ 继承:将子类传递给需要基类的函数并获取子类行为
- 为什么将此 POD 结构用作基类会很危险