我与同事进行了这次谈话,结果很有趣。假设我们有以下POD类
struct A {
void clear() { memset(this, 0, sizeof(A)); }
int age;
char type;
};
clear
旨在清除所有成员,设置为0
(按字节顺序)。如果我们使用A
作为基类,会出现什么问题?这里有一个微妙的漏洞来源。
答案 0 :(得分:17)
编译器可能会向A添加填充字节。因此sizeof(A)
超出char type
(直到填充结束)。但是在继承的情况下,编译器可能不会添加填充字节。因此,对memset
的调用将覆盖子类的一部分。
答案 1 :(得分:4)
除了其他注释之外,sizeof
是一个编译时运算符,因此clear()
不会将派生类添加的任何成员归零(除非由于填充奇怪而指出)。
这没有什么真正“微妙”的;在{+ C ++}中使用memset
是件可怕的事情。在极少数情况下,你真的可以用零来填充内存并期望理智的行为,和你真的需要用零填充内存,和通过以下方式对所有内容进行零初始化初始化列表文明方式在某种程度上是不可接受的,而是使用std::fill
。
答案 2 :(得分:2)
理论上,编译器可以以不同方式布置基类。 C ++03§10第5段说:
基类子对象的布局(3.7)可能与同一类型的派生对象的布局不同。
作为StackedCrooked mentioned,这可能是由于编译器在基类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 %d\n", sizeof(A), sizeof(B), ((char*)&b.x - (char*)&b));
b.x = 3;
b.clear();
printf("%d\n", b.x);
return 0;
}
将A
,B
或两者修改为“已包装”(VS中为#pragma pack
,GCC中为__attribute__((packed))
),我无法获取{{ 1}}在任何情况下都要被覆盖。已启用优化。为尺寸/偏移打印的3个值始终为8/12 / 8,8 / 9/8或5/6/5。
答案 3 :(得分:0)
基类的clear
方法只会设置类成员的值。
根据对齐规则,允许编译器插入填充,以便下一个数据成员将出现在对齐的边界上。因此,type
数据成员之后会有填充。后代的第一个数据成员将占用此插槽并且不受memset
的影响,因为基类的sizeof
不包括后代的大小。父母的大小!=孩子的大小(除非孩子没有数据成员)。 请参见切片。
结构包装不是语言标准的一部分。希望有一个好的编译器,打包结构的大小不包括在最后一个之后的任何额外字节。即便如此,从压缩父节点继承的压缩后代应该产生相同的结果:parent仅设置父节点中的数据成员。
答案 4 :(得分:0)
简要:在我看来,唯一的一个潜在问题在于我无法找到关于"填充字节的任何信息"在C89,C2003标准中保证......他们是否有一些特殊的易失性或只读行为 - 我甚至找不到术语"填充字节"是标准的......
<强烈>详细强>:
对于POD类型的对象,C ++ 2003标准保证:
保证在POD对象的开头没有填充
可以打破C ++规则:goto statement,lifetime
对于C89,还存在一些关于结构的保证:
如果结构具有相同的开头,当用于联合结构的混合时,则第一个组件具有完美的效果
C中的sizeof结构等于存储所有组件的内存量,组件之间填充的位置,在以下结构下放置填充
在结构的C组件中给出地址。可以保证地址的组件按升序排列。并且第一个组件的地址与结构的起始地址一致。无论程序运行的计算机是哪个端点
所以在我看来,这样的规则也适用于C ++,一切都很好。我真的认为在硬件级别没有人会限制你写入非const对象的填充字节。