Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/124.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 为什么将此POD结构用作基类会很危险?_C++_Memset - Fatal编程技术网

C++ 为什么将此POD结构用作基类会很危险?

C++ 为什么将此POD结构用作基类会很危险?,c++,memset,C++,Memset,我和一位同事进行了这次谈话,结果很有趣。假设我们有下面的POD类 struct A { void clear() { memset(this, 0, sizeof(A)); } int age; char type; }; clear用于清除所有成员,设置为0(按字节)。如果我们使用A作为基类,会出现什么问题?这里有一个微妙的bug来源 编译器可能会向A添加填充字节。因此sizeof(A)扩展到了char-type(直到填充结束)。但是,在继承的情况下,编译器可能不会添加

我和一位同事进行了这次谈话,结果很有趣。假设我们有下面的POD类

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

  int age; 
  char type; 
};

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

编译器可能会向A添加填充字节。因此
sizeof(A)
扩展到了
char-type
(直到填充结束)。但是,在继承的情况下,编译器可能不会添加填充字节。因此,对
memset
的调用将覆盖子类的一部分。

除其他注释外,
sizeof
是一个编译时运算符,因此
clear()
不会将派生类添加的任何成员归零(除非由于填充奇怪而注意到)


这并没有什么真正的“微妙”之处<代码> MyStuts<代码>是C++中使用的可怕的东西。在极少数情况下,如果你真的可以用零填充内存并期望正常的行为,你真的需要用零填充内存,零通过初始值设定项列表初始化所有内容,文明的方式在某种程度上是不可接受的,请使用
std::fill
代替,编译器可以对基类进行不同的布局。C++03§10第5段规定:

基类子对象的布局(3.7)可能与同一类型的最派生对象的布局不同

例如,当基类
A
作为自己的对象存在时,编译器可能会在其末尾添加填充,但当它是基类时,编译器可能不会添加该填充。这将导致
A::clear()
覆盖子类成员的前几个字节

然而,在实践中,我无法在GCC或VisualStudio2008中实现这一点。使用此测试:

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和GCC中),我无论如何都无法覆盖
B.x
。已启用优化。为大小/偏移打印的3个值始终为8/12/8、8/9/8或5/6/5。

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

根据对齐规则,允许编译器插入填充,以便下一个数据成员出现在对齐的边界上。因此,
类型
数据成员后将有填充。子体的第一个数据成员将占用此插槽,并且不受
memset
的影响,因为基类的
sizeof
不包括子体的大小。父级的大小!=子级的大小(除非子级没有数据成员)请参见切片


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

简单地说:在我看来,唯一的潜在问题是我在C89中找不到任何关于“填充字节”保证的信息,C2003标准件…它们是否有一些异常的易失性或只读行为-我甚至找不到“填充字节”一词在标准件中的含义

详细信息

对于POD类型的对象,C++2003标准保证:

  • 当您将对象的内容memcpy到char或unsigned char数组中,然后将内容memcpy回对象中时,对象将保留其原始值
  • 保证POD对象的开头没有填充

  • 可以打破关于GOTO语句、生命周期

    的C++规则
对于C89,还存在一些关于结构的保证:

  • 当用于联合结构的混合时,如果结构具有相同的起始,则第一个组件具有完美的匹配

  • C中的sizeof structures等于存储所有组件的内存量、组件之间的填充下的位置、以下结构下的填充下的位置

  • 在C语言中,结构中的组件都有地址。可以保证地址的组成部分按升序排列。第一组件的地址与结构的起始地址一致。不管程序运行的计算机是哪一个端点


因此,我觉得这样的规则对C++也是适用的,一切都很好。我真的认为,在硬件级别,没有人会限制您为非常量对象写入填充字节。

@Luchian我不明白您的意思?我认为
sizeof(int)+sizeof(char)=5
在32位体系结构上不一致。@Luchian:你认为
sizeof(a)
会是什么?答案可能会让你吃惊。@Fred Larson sizeof(A)取决于他平台上的sizeof(int)。同样,在本例中,它是sizeof(int)+sizeof(char)。如果成员顺序颠倒,它将是sizeof(char)+int_padding-1+sizeof(int)。@Luchian:再猜一次。试试看。你说得对,我以为只在成员之间添加填充以保持对齐。生活和学习。谢谢,先生们!:)“我真的希望你能给我们提供你的答案,约翰。”弗雷德下面已经给出了答案。所以我没有必要“释放”它。我不认为
std::fill
在这种特殊情况下会有什么帮助-你会使用什么迭代器?
char*begin=reinterpret\u cast(这个);char*end=begin+sizeof(此);标准::填充(开始、结束、0)应该是惯用的翻译,但它确实有同样的问题。老实说,我可能宁愿做一个