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.
A struct cannot inherit from another struct or class, and it cannot be the base of a class.
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With