4
votes

I am not familiar enough with the memory layout of objects that contain virtual bases to understand why the following appears to be compiled incorrectly by both clang and gcc. This is an academic exercise, so please excuse the frivolity of memset() in a constructor. I am testing using Linux x86-64 with both clang 7 and gcc 8.2:

#include <cstring>

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

    int i;
    char a;
};

struct B { char b = 'b'; };
struct C : virtual B, A {};

char foo() {
    C c;
    return c.b;
}

When compiled with the -O2 -Wall -pedantic -std=c++17, both compilers produce the following assembly with no warnings:

foo():
    xor     eax, eax
    ret

Changing C to not inherit B virtually or changing sizeof(A) to 5 or less in the call to memset both change the compilers' output to return 'b', as I'd expect:

foo():
    mov     al, 98     # gcc uses eax directly, here
    ret

What is the memory layout of C when it derives from B virtually/non-virtually, and are these compilers wrong by allowing A's constructor to zero out members of a different base class? I know the layout isn't defined by the standard, but I would expect all implementations ensure that a class' constructor cannot interfere with data members of an unrelated class, even when used in multiple inheritance like this. Or at least warn that something like this might happen. (gcc's new -Wclass-memaccess warning isn't diagnosed here).

If it comes down to memset(this, 0, sizeof(A)) being invalid in a constructor, then I'd expect the compilers to either fail to compile or to at least warn.

Link: https://godbolt.org/z/OSQV1j

1
Why would the compiler refuse to compile your buggy code?curiousguy
You might look at layout done by your compiler by something like that Demo. In that case, compiler uses padding of A to place B as optimization.Jarod42
Hard to diagnose all possible errors. (Moreover the memset could be legit in some cases, code might be split in different TU making the check harder or even impossible).Jarod42
@Jarod42 memset is OK when constructing a complete object (or array element or member subobject).curiousguy
Do you need a practical answer or a standard based answer?curiousguy

1 Answers

-1
votes

I am not familiar enough with the memory layout of objects that contain virtual bases

Virtual bases (and base class subobjects that have virtual bases) are not in general constructed nor represented like complete objects of the same types, as opposed to any other subobject which has the same layout (the relative position of every subobject of that base class subobject) as a complete object with the same type:

  • array element
  • class member
  • non virtual base class subobject that has no virtual base class

which are all constructed and represented like a complete object.

Clarification: although the constructor for a base class subobject is usually the same as for a complete object, the memory reserved exclusively to a base class subobject may be smaller than its normal size.

to understand why the following appears to be compiled incorrectly by both clang and gcc.

You haven't posted any evidence of bad code generation.

This is an academic exercise, so please excuse the frivolity

It isn't frivolous, it's plainly wrong.

memset() in a copy constructor.

Doing that destroys the object by overwriting it.

The code uses an unsupported operation (overwriting the memory of the c2 object during construction) and the compiler does not have warn you that your code uses an object whose lifetime was ended by a call to low level memory access function (memset). Ending the lifetime inside the constructor of a base class is illegal: technically the lifetime has not even began when you end it.

If you want to end the lifetime of an object by overwriting it, do it after construction.

Summary:

There is no guarantee that every subobject of type T "own" sizeof (T) bytes and can overwrite those; this is however guaranteed for array elements and members.