Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can it be dangerous to use this POD struct as a base class?

Tags:

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.

like image 253
Johannes Schaub - litb Avatar asked Aug 18 '11 20:08

Johannes Schaub - litb


People also ask

Can struct be used as base class for another class?

A struct cannot inherit from another struct or class, and it cannot be the base of a class.

Can you do inheritance with structs?

Structs cannot have inheritance, so have only one type. If you point two variables at the same struct, they have their own independent copy of the data. With objects, they both point at the same variable.


3 Answers

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.

like image 161
StackedCrooked Avatar answered Oct 06 '22 18:10

StackedCrooked


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.

like image 45
Karl Knechtel Avatar answered Oct 06 '22 19:10

Karl Knechtel


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.

like image 35
Adam Rosenfield Avatar answered Oct 06 '22 17:10

Adam Rosenfield