7

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. Sep 25 '14 at 05:04
  • 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 Sep 25 '14 at 05:04
  • 1
    @Wlerin X is not trivially copyable. – T.C. Sep 25 '14 at 05:24
  • @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 Sep 25 '14 at 05:41
  • 1
    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 - on strike Sep 25 '14 at 06:23
  • @Wlerin "It has a copy constructor that does nothing" No, it has a copy constructor that prints something to standard output. Anyway, like other trivial constructors, a trivial copy constructor is specifically defined as _not user-defined_. Oddly can't find a relevant SO reference for this, but here: http://en.cppreference.com/w/cpp/language/copy_constructor – underscore_d Jun 16 '16 at 07:55
  • @underscore_d Yes, I understand that. However, it can still be memcpy'd without UB. When this discussion originally took place, cppreference gave the impression that any use of memcpy on an object with a non-trivial copy constructor was undefined behaviour. However, [it seems this was never actually in the standard](http://stackoverflow.com/questions/29777492/why-would-the-behavior-of-stdmemcpy-be-undefined-for-objects-that-are-not-triv) and the docs [have since been updated](http://en.cppreference.com/w/cpp/string/byte/memcpy). – Wlerin Jun 16 '16 at 10:47
  • @underscore_d Also the copy constructor "does nothing" in the sense that it does nothing that could lead to undefined behaviour (like allocating memory) when the objects' destructors are called. – Wlerin Jun 16 '16 at 10:55
  • @Wlerin lmao, really? Firstly, cppreference is far from a 1st-class source, depending on who's editing it and whether they're wrong. Secondly, that page has been changed **back** since you last read it, and it _now_ says this _is_ UB - or at best unspecified - while linking back to that same thread. What a sorry mess. Lastly, just read the Standard. Since it apparently needs to be rehashed yet another time: **N3797 3.9 points 2~4 only allow for trivially copyable types to be copied by `memcpy`.** By omission, other types are 'not defined', which as cppreference says, is unspecified _at best_. – underscore_d Jun 16 '16 at 11:10
  • @Wlerin and if it's not UB but instead _unspecified_ - big deal! Both are equally useless if you're trying to write reliable code that won't blow your foot off in a year or two. Sure, you can lock yourself into any compiler who choose to specify their behavior in this case - if any do - and just hope they continue to specify it in the same way for all future releases. I don't rate that as much of an upgrade. This is why the Standard exists. And if it doesn't say what happens in some case, that case is dangerous and should not be recommended if it can be avoided... which it usually can. – underscore_d Jun 16 '16 at 11:17
  • @underscore_d That being the case, no one is suggesting this should be used in real code, just that it didn't offer any examples of UB in the copying process. – Wlerin Jun 16 '16 at 12:30
  • @Wlerin In fact, no, rather than the default being _unspecified_: the default is UB **[defns.undefined]**. So this **is** UB, by virtue of not having been deliberately defined. Unspecified isn't the default, which was the conclusion drawn by the commentary on cppreference. Not that "unspecified" is a useful category anyway; it looks to me like more of a 'necessary evil' fallback for stuff the committee don't want to think about. – underscore_d Jun 16 '16 at 13:00
  • Related (my own answer): how you can copy objects with "placement new" which you are not supposed to copy with `memcpy()`: [What uses are there for “placement new”?](https://stackoverflow.com/questions/222557/what-uses-are-there-for-placement-new/63893849#63893849) – Gabriel Staples Sep 16 '20 at 01:21

2 Answers2

10

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;
}
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • Indeed, passing an invalid pointer to the operator delete produces UB. Clear example, Thank you. –  Sep 25 '14 at 16:52
  • @DmitryFucintv, you are welcome. Glad I could answer your question. – R Sahu Sep 25 '14 at 16:57
6

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.

Tom
  • 7,269
  • 1
  • 42
  • 69