我和一位同事进行了这次对话,结果很有意思。说我们有以下POD类为什么使用这个POD结构体作为基类会很危险?
struct A {
void clear() { memset(this, 0, sizeof(A)); }
int age;
char type;
};
clear
旨在清除所有成员,设置为0
(字节英明)。如果我们使用A
作为基类,会出现什么问题?这里有一个微妙的错误来源。
我和一位同事进行了这次对话,结果很有意思。说我们有以下POD类为什么使用这个POD结构体作为基类会很危险?
struct A {
void clear() { memset(this, 0, sizeof(A)); }
int age;
char type;
};
clear
旨在清除所有成员,设置为0
(字节英明)。如果我们使用A
作为基类,会出现什么问题?这里有一个微妙的错误来源。
编译器很可能会将填充字节添加到A.所以sizeof(A)
扩展超出char type
(直到填充结束)。但是,在继承的情况下,编译器可能不会添加填充的字节。所以调用memset
将会覆盖部分子类。
@Luchian我不明白你的意思吗?我认为'sizeof(int)+ sizeof(char)= 5'在32位架构上没有对齐。 – StackedCrooked
@Luchian:你认为'sizeof(A)'会是什么?答案可能会让你大吃一惊。 –
@Fred Larson sizeof(A)取决于他的平台上的sizeof(int)。再次,在这种情况下,它是sizeof(int)+ sizeof(char)。如果成员顺序颠倒了,它将是sizeof(char)+ int_padding - 1 + sizeof(int)。 –
除了其他说明,sizeof
是一个编译时操作符,因此clear()
不会将由派生类添加的任何成员(由于填充奇怪而引起的除外)清零。
没有什么真正的“微妙”这个;在C++中使用memset
是一件可怕的事情。在极少数情况下,你真的可以用零填充内存,并期望理智的行为,和你真的需要用零填充内存,和零初始化所有的东西通过初始化列表文明的方式是不可接受的,使用改为std::fill
。
在这个特殊情况下,没有看到'std :: fill'如何帮助你 - 你会使用什么迭代器? – Voo
'char * begin = reinterpret_cast
@Karl:“*老实说,我可能宁愿为原始类型创建自动归零初始化包装*”请参见[Boost.ValueInitialized](http://www.boost.org/doc/libs/release/libs/utility /value_init.htm)。 – ildjarn
从理论上讲,编译器可以不同奠定了基类。 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
和__attribute__((packed))
在GCC),我不能让b.x
在任何情况下被覆盖。优化已启用。为尺寸/偏移打印的3个值始终为8/12/8,8/9/8或5/6/5。
这是我的测试用例。虽然基地不是POD:http://ideone.com/GHATf。起初,我没有注意到海湾合作委员会对于POD行为会有所不同,所以我以一种使问题更加突出的方式提出了问题。但正如你所说,这种可能性依然存在。我敢打赌,很多人会认为一个愚蠢的“私人”没有任何效果。另外值得注意的是3.9p3和3.9p2,其中包含备注“......其中obj1和obj2都不是基类子对象......”以支持不同的布局。 –
基类的方法clear
将只设置类成员的值。
根据对准规则,编译器允许使得下一数据成员将对准的边界上发生插入填充。因此在数据成员type
之后会有填充。后代的第一个数据成员将占用该插槽和从的memset
影响自由,因为sizeof
基类不包括后代的大小。父母大小!=孩子的大小(除非孩子没有数据成员)。 参见切片。
结构的包装不是语言标准的一部分。希望通过一个好的编译器,压缩结构的大小不会在最后一个字节后包含任何额外的字节。即便如此,从打包父项继承的打包子孙也应该产生相同的结果:父项只设置父项中的数据成员。
简而言之:在我看来,唯一的一个potentional的问题是,我无法找到关于“填充字节”在C89保证任何信息,C2003 standarts ....他们有一些特殊的挥发性或只读行为 - 我甚至不能找到什么术语“填充字节”,由standarts的意思是...
详细:
对于POD类型是由C++ 2003的标准,保障对象:
保证在开始时不会有填充一个POD对象的
可以打破约C++规则:goto语句,寿命
对于C89这里还存在着一些保证约结构:
当联合结构的混合物使用,如果结构体具有相同的开始时,则第一元件库具有完美mathing
的sizeof结构在C等于的存储器来存储所有的组分的量,根据该位置在组件之间填充,在以下结构下放置填充
在C结构的组件中给出地址。有保证地址的组成部分按升序排列。第一个组件的地址与结构的起始地址一致。无论程序运行在哪台电脑上
所以在我看来,这样的规则也适用于C++,并且一切都很好。我真的认为,在硬件层面,没有人会限制你为非const对象填充填充字节。
我确实希望你能给我们提供你的答案,Johannes。 –
@Fred的答案已经在下面给出。所以我不需要“释放”它。 –