c++constructorlanguage-lawyermultiple-inheritancememset

Is it legal to zero empty C++ classes in the constructor and inherit from them?


It is not very rare to find a library that zeros its objects in the constructors using memset. And sometimes it happens to empty classes as well. Is it safe, especially when one inherits such classes?

Consider the following example:

#include <iostream>
#include <cstring>

struct A { char a; };

struct B {
  B() { std::memset( this, 0, sizeof(B) ); } 
};

struct C : A, B {};

static_assert( sizeof(C) == 1 );

int main() {
    std::cout << (int)C{ {1},  {} }.a
              << (int)C{ {1}, B{} }.a;
}

GCC prints here 00, Clang prints 01 and MSVC prints 11 while emitting

warning C4789: buffer '' of size 1 bytes will be overrun; 1 bytes will be written starting at offset 1

Online demo: https://gcc.godbolt.org/z/jW1edecrd

Is it defined behavior? And if yes, which compiler is correct here?


Solution

  • This is undefined.

    The only objects for which you can copy bytes over them and the standard guarantees anything are objects of trivially copyable types which are not potentially overlapping subobjects (ref):

    For any object (other than a potentially-overlapping subobject) of trivially copyable type T, whether or not the object holds a valid value of type T, the underlying bytes making up the object can be copied into an array of char, unsigned char, or std​::​byte. If the content of that array is copied back into the object, the object shall subsequently hold its original value.

    For two distinct objects obj1 and obj2 of trivially copyable type T, where neither obj1 nor obj2 is a potentially-overlapping subobject, if the underlying bytes making up obj1 are copied into obj2,29 obj2 shall subsequently hold the same value as obj1.

    A potentially-overlapping subobject is (ref):

    A potentially-overlapping subobject is either:

    • a base class subobject, or
    • a non-static data member declared with the no_unique_address attribute.

    So, according to the standard, this is not safe for any class usable as a base class.

    It's also worth adding that this does not require an empty class to have overlapping storage in practice either:

    class A {
        void* p;
        int a;
    };
    
    class B : A {
        int a;
    };
    
    static_assert(sizeof(A) == 16);
    static_assert(sizeof(B) == 16);
    

    B::a is stored in the tail padding for A on Linux. godbolt