4
votes

The standard defines we can use std::memcpy int the following way:

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.

What potential problem we could get if we applied that function to an object of non-trivially copyable type? The following code works as if it worked for trivially-copyable type:

#include <iostream>
#include <cstring>

using std::cout;
using std::endl;

struct X
{
    int a = 6;
    X(){ }
    X(const X&)
    {
        cout << "X()" << endl;
    }
};

X a;
X b;
int main()
{
    a.a = 10;
    std::memcpy(&b, &a, sizeof(X));
    cout << b.a << endl; //10
}

DEMO

2
Obvious UB is obvious. Consider memcpy'ing a std::vector, for instance. - T.C.
Well, yes, it works because struct X is trivially copyable. If the structure's members included any kind of heap-allocated memory, for example, then you would encounter problems. Well, in a real program. Not in your test. - Wlerin
@Wlerin X is not trivially copyable. - T.C.
@T.C. Mmm. It has a copy constructor that does nothing. That might be enough to technically make it not trivially copyable, but the actual contents of the class can be copied member by member without problems. Pre C++11, the result of b = a would also be identical to the result of memcpy. Either way this sample program is not a good example. - Wlerin
What is your actual question? Do you want us to explain to you what undefined behavior is? Or do you just want confirmation that this is, in fact, undefined behavior? - Cody Gray♦

2 Answers

7
votes

You asked:

What potential problem we could get if we applied that function to an object of non-trivially copyable type?

Here's a very simple example that illustrates the problem of using std::memcpy for objects of non-trivially copyable type.

#include <cstring>

struct A
{
   A(int size) : size_(size), data_(new int[size]) {}
   ~A() { delete [] data_; }

   // The copy constructor and the copy assignment operator need
   // to be implemented for the class too. They have been omitted
   // to keep the code here minimal.

   int size_;
   int* data_;
};

int main()
{
   A a1(10);
   A a2(20);
   std::memcpy(&a1, &a2, sizeof(A));

   // When we return from the function, the original data_ of a1
   // is a memory leak. The data_ of a2 is deleted twice.

   return 0;
}
5
votes

Consider this program:

#include <memory>

int main() {
    std::shared_pointer<int> x(new int);

    {
        std::shared_pointer<int> y;
        memcpy((void*)&y, (void*)&x, sizeof(x));
    }

    *x = 5;
}

Because we copied x to y using memcpy instead of the assignment operator, the reference counts did not get updated. So, at the end of that block, the destructor of y is called. It finds that it has a reference count of 1, meaning it is the only shared_pointer instance pointing to the heap-allocated integer. So it deletes it.

The last line of main will likely segfault, because x points to an object that has been deleted.