为什么使用这个 POD 结构作为基类会很危险?

发布于 2024-11-30 09:02:10 字数 266 浏览 1 评论 0原文

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

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

  int age; 
  char type; 
};

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

I had this conversation with a colleague, and it turned out to be interesting. Say we have the following POD class

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

  int age; 
  char type; 
};

clear is intended to clear all members, setting to 0 (byte wise). What could go wrong if we use A as a base class? There's a subtle source for bugs here.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

烟─花易冷 2024-12-07 09:02:10

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

The compiler is likely to add padding bytes to A. So sizeof(A) extends beyond char type (until the end of the padding). However in case of inheritance the compiler might not add the padded bytes. So the call to memset will overwrite part of the subclass.

兔小萌 2024-12-07 09:02:10

除了其他注释之外,sizeof 是一个编译时运算符,因此 clear() 不会将派生类添加的任何成员清零(除非由于填充而指出)怪异)。

这并没有什么真正“微妙”的地方。在 C++ 中使用 memset 是一件可怕的事情。在极少数情况下,您确实可以用零填充内存并期望正常的行为,您确实需要用零填充内存,通过以下方式对所有内容进行零初始化:初始化列表文明的方式在某种程度上是不可接受的,请使用 std::fill 代替。

In addition to the other notes, sizeof is a compile-time operator, so clear() will not zero out any members added by derived classes (except as noted due to padding weirdness).

There's nothing really "subtle" about this; memset is a horrible thing to be using in C++. In the rare cases where you really can just fill memory with zeros and expect sane behaviour, and you really need to fill the memory with zeros, and zero-initializing everything via the initializer list the civilized way is somehow unacceptable, use std::fill instead.

那请放手 2024-12-07 09:02:10

理论上,编译器可以以不同的方式布局基类。 C++03 §10 第 5 段说:

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

作为StackedCrooked 提到,当基类 A 作为自己的对象存在时,编译器在基类 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 或两者修改为“打包”(带有 #pragma pack 在 VS 和 __attribute__((packed)) in GCC),在任何情况下我都无法覆盖 bx 。优化已启用。打印的尺寸/偏移量的 3 个值始终为 8/12/8、8/9/8 或 5/6/5。

In theory, the compiler can lay out base classes differently. C++03 §10 paragraph 5 says:

A base class subobject might have a layout (3.7) different from the layout of a most derived object of the same type.

As StackedCrooked mentioned, this might happen by the compiler adding padding to the end of the base class A when it exists as its own object, but the compiler might not add that padding when it's a base class. This would cause A::clear() to overwrite the first few bytes of the members of the subclass.

However in practice, I have not been able to get this to happen with either GCC or Visual Studio 2008. Using this test:

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

And modifying A, B, or both to be 'packed' (with #pragma pack in VS and __attribute__((packed)) in GCC), I couldn't get b.x to be overwritten in any case. Optimizations were enabled. The 3 values printed for the sizes/offsets were always 8/12/8, 8/9/8, or 5/6/5.

失去的东西太少 2024-12-07 09:02:10

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

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

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

The clear method of the base class will only set the values of the class members.

According to alignment rules, the compiler is allowed to insert padding so that the next data member will occur on the aligned boundary. Thus there will be padding after the type data member. The first data member of the descendant will occupy this slot and be free from the effects of memset, since the sizeof the base class does not include the size of the descendant. Size of parent != size of child (unless child has no data members). See slicing.

Packing of structures is not a part of the language standard. Hopefully, with a good compiler, the size of a packed structure does not include any extra bytes after the last. Even so, a packed descendant inheriting from a packed parent should produce the same result: parent sets only the data members in the parent.

怕倦 2024-12-07 09:02:10

简述:在我看来,唯一一个潜在的问题是我在 C89、C2003 标准中找不到任何有关“填充字节”保证的信息……它们是否有一些非凡的易失性或只读行为 - 我什至找不到标准中术语“填充字节”的含义...

详细:

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

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

  • 可以打破关于:goto 语句、生命周期的 C++ 规则

对于 C89 有还存在有关结构的一些保证:

  • 当用于联合结构的混合时,如果结构具有相同的开头,则第一个组件具有完美的数学

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

  • 在C中,结构体的组件都给出了地址。保证地址的组成部分按升序排列。第一个组件的地址与结构体的起始地址一致。无论程序运行的计算机采用哪种字节序,

,所以在我看来,这样的规则也适用于 C++,而且一切都很好。我真的认为在硬件级别没有人会限制您为非常量对象写入填充字节。

Briefly: It seems to me that the only one potentional problem is in that I can not found any info about "padding bytes" guarantees in C89, C2003 standarts....Do they have some extraordinary volatile or readonly behavior - I can not find even what does term "padding bytes" mean by the standarts...

Detailed:

For objects of POD types it is guaranteed by the C++2003 standard that:

  • when you memcpy the contents of your object into an array of char or unsigned char, and then memcpy the contents back into your object, the object will hold its original value
  • guaranteed that there will be no padding in the beginning of a POD object

  • can break C++ rules about: goto statement, lifetime

For C89 there is also exist some guarantees about structures:

  • When used for a mixture of union structures if structs have same begining, then first compoments have perfect mathing

  • sizeof structures in C is equal to the amount of memory to store all the components, the place under the padding between the components, place padding under the following structures

  • In C components of the structure are given addresses. There is a guarantee that the components of the address are in ascending order. And the address of the first component coincides with the start address of the structure. Regardless of which endian the computer where the program runs

So It seems to me that such rules is appropriate to C++ also, and all is fine. I really think that in hardware level nobody will restrict you from write in padding bytes for non-const object.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文