2011-08-18 62 views
23

我和一位同事进行了这次对话,结果很有意思。说我们有以下POD类为什么使用这个POD结构体作为基类会很危险?

struct A { 
    void clear() { memset(this, 0, sizeof(A)); } 

    int age; 
    char type; 
}; 

clear旨在清除所有成员,设置为0(字节英明)。如果我们使用A作为基类,会出现什么问题?这里有一个微妙的错误来源。

+0

我确实希望你能给我们提供你的答案,Johannes。 –

+0

@Fred的答案已经在下面给出。所以我不需要“释放”它。 –

回答

17

编译器很可能会将填充字节添加到A.所以sizeof(A)扩展超出char type(直到填充结束)。但是,在继承的情况下,编译器可能不会添加填充的字节。所以调用memset将会覆盖部分子类。

+0

@Luchian我不明白你的意思吗?我认为'sizeof(int)+ sizeof(char)= 5'在32位架构上没有对齐。 – StackedCrooked

+0

@Luchian:你认为'sizeof(A)'会是什么?答案可能会让你大吃一惊。 –

+0

@Fred Larson sizeof(A)取决于他的平台上的sizeof(int)。再次,在这种情况下,它是sizeof(int)+ sizeof(char)。如果成员顺序颠倒了,它将是sizeof(char)+ int_padding - 1 + sizeof(int)。 –

4

除了其他说明,sizeof是一个编译时操作符,因此clear()不会将由派生类添加的任何成员(由于填充奇怪而引起的除外)清零。

没有什么真正的“微妙”这个;在C++中使用memset是一件可怕的事情。在极少数情况下,你真的可以用零填充内存,并期望理智的行为,你真的需要用零填充内存,零初始化所有的东西通过初始化列表文明的方式是不可接受的,使用改为std::fill

+0

在这个特殊情况下,没有看到'std :: fill'如何帮助你 - 你会使用什么迭代器? – Voo

+0

'char * begin = reinterpret_cast (this); char * end = begin + sizeof(this); std :: fill(begin,end,0);''''''''''''''''''''''''''''''''老实说,我可能宁愿只为原始类型做自动归零初始化包装,并从它们中构建结构。但关键是'std :: fill'是一个更加复杂的填充内存的工具,并且在普通的旧'memset'可以工作的情况下,只需要一个体面的编译器就无需花费任何代价。 –

+0

@Karl:“*老实说,我可能宁愿为原始类型创建自动归零初始化包装*”请参见[Boost.ValueInitialized](http://www.boost.org/doc/libs/release/libs/utility /value_init.htm)。 – ildjarn

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; 
} 

和修改AB,或两者被“打包” (在VS #pragma pack__attribute__((packed))在GCC),我不能让b.x在任何情况下被覆盖。优化已启用。为尺寸/偏移打印的3个值始终为8/12/8,8/9/8或5/6/5。

+1

这是我的测试用例。虽然基地不是POD:http://ideone.com/GHATf。起初,我没有注意到海湾合作委员会对于POD行为会有所不同,所以我以一种使问题更加突出的方式提出了问题。但正如你所说,这种可能性依然存在。我敢打赌,很多人会认为一个愚蠢的“私人”没有任何效果。另外值得注意的是3.9p3和3.9p2,其中包含备注“......其中obj1和obj2都不是基类子对象......”以支持不同的布局。 –

0

基类的方法clear将只设置类成员的值。

根据对准规则,编译器允许使得下一数据成员将对准的边界上发生插入填充。因此在数据成员type之后会有填充。后代的第一个数据成员将占用该插槽和从的memset影响自由,因为sizeof基类不包括后代的大小。父母大小!=孩子的大小(除非孩子没有数据成员)。 参见切片

结构的包装不是语言标准的一部分。希望通过一个好的编译器,压缩结构的大小不会在最后一个字节后包含任何额外的字节。即便如此,从打包父项继承的打包子孙也应该产生相同的结果:父项只设置父项中的数据成员。

0

简而言之:在我看来,唯一的一个potentional的问题是,我无法找到关于“填充字节”在C89保证任何信息,C2003 standarts ....他们有一些特殊的挥发性或只读行为 - 我甚至不能找到什么术语“填充字节”,由standarts的意思是...

详细

对于POD类型是由C++ 2003的标准,保障对象:

  • 当你将你的对象的内容memcpy变成一个char或unsigned char数组,然后将memcpy内容放回到你的对象中时,该对象将保持其原始值
  • 保证在开始时不会有填充一个POD对象的

  • 可以打破约C++规则:goto语句,寿命

对于C89这里还存在着一些保证约结构:

  • 当联合结构的混合物使用,如果结构体具有相同的开始时,则第一元件库具有完美mathing

  • 的sizeof结构在C等于的存储器来存储所有的组分的量,根据该位置在组件之间填充,在以下结构下放置填充

  • 在C结构的组件中给出地址。有保证地址的组成部分按升序排列。第一个组件的地址与结构的起始地址一致。无论程序运行在哪台电脑上

所以在我看来,这样的规则也适用于C++,并且一切都很好。我真的认为,在硬件层面,没有人会限制你为非const对象填充填充字节。

相关问题