8

3.9/2:

For any object (other than a base-class subobject) of trivially copyable type T, whether or not the object holds a valid value of type T, the underlying bytes (1.7) making up the object can be copied into an array of char or unsigned char.

3.9/3:

For any trivially copyable type T, if two pointers to T point to distinct T objects obj1 and obj2, where neither obj1 nor obj2 is a base-class subobject, if the underlying bytes (1.7) making up obj1 are copied into obj2, obj2 shall subsequently hold the same value as obj1.

I understand these rules formally, but I'm interested what is the point of such restrictions?

  • 2
    Probably because of the empty base optimization. – T.C. Sep 13 '14 at 13:49
  • @T.C. It's not quite clear. Could you provide an example to explain? –  Sep 13 '14 at 13:52
  • 1
    @DmitryFucintv Given `struct T {}; struct D : T { int x; };`, `sizeof(T) == 1`, but in `D` the base class subobject may have zero size. – T.C. Sep 13 '14 at 14:00
  • I believe TC has a point, I didn't know of EBO but sounds definitely the reason for avoiding base class subobjects – Marco A. Sep 13 '14 at 14:02
  • @T.C. I know what empty base class optimization is. I meant I would like to look at an example which can cause a problem. –  Sep 13 '14 at 14:03
  • 1
    @DmitryFucintv That *is* an example where it would cause a problem. Using `memcpy` to copy the `T` subobject to another `D` object would overwrite one byte of `x`. –  Sep 13 '14 at 14:05
  • @hvd Indeed. I just didn't understand what T.C. means. –  Sep 13 '14 at 14:08

2 Answers2

9

Base class subobjects may have padding at the end that gets used by a derived class. Given two classes,

struct A {
  int a;
  char b;
};
struct B : A {
  char c;
};

it's entirely possible that sizeof(A) == sizeof(B). If they are equal, it should be clear that things break if you simply use memcpy to copy the A subobject: you wouldn't be able to prevent reading or even overwriting the c value.

Your implementation may or may not re-use padding like that. A valid reason for designing an ABI where the padding is not re-used is precisely to deal nicely with code that does, incorrectly, use memcpy for such subobjects.

The comments give an example with empty base classes. That's one particular case where current implementations are very likely to re-use a byte of the base class, but it's not the only time it's allowed.

0

Here's an example of value-screwing due to base class subobjects and EBO:

#include <cassert>
#include <iostream>

struct Base {}; // empty class

struct Derived1 : Base {
public:    
    int i;
};

int main()
{
    // the size of any object of empty class type is at least 1
    assert(sizeof(Base) == 1);

    // empty base optimization applies
    assert(sizeof(Derived1) == sizeof(int));

    Base objBase;
    Derived1 objDerived;
    objDerived.i = 42;
    Base& refToobjDerived = objDerived;

    char buf[sizeof(Base)]; // 1

    std::memcpy(buf, &objBase, sizeof(Base)); // copy objBase to buf
    // might do something with buf..
    std::memcpy(&refToobjDerived, buf, sizeof(Base)); // EBO! I'm overwriting the int's value!

    std::cout << objDerived.i; // Screwed
}

Example

If you render the base class non-trivially-copyable, the value will not be touched.

Another problem as hvd highlighted could lie in additional padding at the end of the base class that gets used to store derived-owned data.

Marco A.
  • 43,032
  • 26
  • 132
  • 246