5

std::aligned_storage::type is POD type. POD type can memcpy. However, What happens if placement new non-trivially-copyable type to std::aligned_storage? Can it memcpy that std::aligned_storage?

non-trivially-copyable type(non-POD type) can NOT memcpy, Behavior is undefined. If std::aligned_storage memcpy non-trivially-copyable type, is it also undefined behavior?

#include <new>
#include <type_traits>
#include <cstring>
#include <iostream>

struct y { int a; } ;

// non-trivially-copyable
struct t
{
    y a;
    int* p;
    t(){ p = new int{ 300 }; }
    t( t const& ){ a.a += 100; }
    ~t(){ delete p; }
};

int main()
{   // Block 1
    { 
        t a; a.a.a = 100;
        t b; b.a.a = 200;
        // std::memcpy(&b,&a,sizeof(t));  // abort...non-trivially-copyable
    }
    // Block 2
    {
        std::aligned_storage_t<sizeof(t),alignof(t)> s;
        {
            t a;
            a.a.a = 100;
            std::memcpy(&s,&a,sizeof(t)); // OK...Coincidence? Behavior is undefined?
        }
        std::cout << static_cast<t*>(static_cast<void*>(&s))->a.a << std::endl; // OK...
    }
    // Block 3
    {
        std::aligned_storage_t<sizeof(t),alignof(t)> s1; new( &s1 ) t; 
        std::aligned_storage_t<sizeof(t),alignof(t)> s2; new( &s2 ) t;

        std::memcpy(&s2,&s1,sizeof(t)); // trivially-copyable????
    }
}

I thought that it is undefined as well. However, we have work. Is this for coincidence?

M.M
  • 138,810
  • 21
  • 208
  • 365
Cocoa
  • 95
  • 4
  • `memcpy()` takes memory addresses, careless if these are aligned or not. – πάντα ῥεῖ Jun 15 '15 at 22:04
  • Hi. The [guidelines for posting](http://stackoverflow.com/help/how-to-ask) on SO are that the code should appear in the post, and it should be *Minimal* to show the problem. I have transcribed your code and simplified it a little, hopefully it still shows the same behaviour for you. – M.M Jun 15 '15 at 22:32
  • @MattMcNabb Thank you – Cocoa Jun 15 '15 at 22:39

1 Answers1

7

First of all: as discussed on this thread, the C++ standard only defines the behaviour of memcpy for trivially copyable objects. That thread gives a specific example of how it can break for non-trivially copyable options

So, a narrow interpretation of the Standard would say that the mere act of calling memcpy causes UB.

However, a more common sense interpretation would be that it's OK to copy the bytes, but any attempt to treat the target as actually containing an object of the same type would cause UB. Especially in the case where the program depends on side-effects of the destructor, as yours does. The rest of my answer is based on this latter interpretation.


Starting with Block 2:

std::aligned_storage_t<sizeof(t),alignof(t)> s;
{
    t a;
    a.a.a = 100;
    std::memcpy(&s,&a,sizeof(t)); 
}
std::cout << static_cast<t*>(static_cast<void*>(&s))->a.a << std::endl

Since t is not trivially copyable, we have not created a valid t object in s's storage. So the attempt to use s as if it contained a valid t object certainly causes undefined behaviour. When UB occurs any results can follow, including (but not limited to) it appearing to "work as expected".


In Block 1 (if the memcpy is uncommented):

{
    t a; a.a.a = 100;
    t b; b.a.a = 200;
    std::memcpy(&b,&a,sizeof(t));  // abort...non-trivially-copyable
}

Here we have destructor side-effects. The memcpy ends the lifetime of a (because a's storage is re-used). However the code will go on to try and call the destructor on a.

In this case, even if the copy "appears to work", your abort probably comes from the double-free of a.p.

If we changed t to be a non-trivially-copyable type but with no destructor side-effects then this example would be unclear.

There is no such double-free in Block 2 because no destructor is ever invoked for the t stored in s.


Block 3:

This is similar to Block 2: the memcpy does not create an object. It "appears to work" because you never invoke destructors for the objects in the aligned_storage.

In fact, under our common-sense interpretation of memcpy, there is no UB here because you never attempted to use the result of copying the bytes and the target does not have a destructor called on it. (If you copied your cout line to here, it would cause UB for the same reason as in Block 2).


Related discussion: Even for trivially copyable classes it is still murky

The C++ standard is unclear around the issues of when object lifetime begins for objects in malloc'd space or aligned_storage. There was a submission N3751 recognizing that this needs cleanup but there is still a lot of work to do.

In your Block 2, the lifetime has not begun for s. This is because t has non-trivial initialization. (This is actually not clearly stated by the C++ standard either). However Block 1's a is an object whose lifetime has begun.

N3751 proposes that (if t were trivially copyable) then the memcpy would in fact begin the lifetime of s.

Community
  • 1
  • 1
M.M
  • 138,810
  • 21
  • 208
  • 365
  • Wasn't there a long debate on SO recently over whether just `memcpy`ing the bytes out of a non-trivially-copyable type is UB? – T.C. Jun 15 '15 at 23:57
  • @T.C. copying *out of*? Surely not. I didn't see any recent thread other than the one linked in my answer though. – M.M Jun 15 '15 at 23:58
  • [Fun debate](http://stackoverflow.com/questions/29777492/why-would-the-behavior-of-stdmemcpy-be-undefined-for-objects-that-are-not-triv). I don't think either Block 2 or Block 3 has UB (in both cases, you copy a bunch of bytes out of a `T` into some storage, which is just fine). – T.C. Jun 16 '15 at 00:04
  • @T.C. sorry, I should clarify that in Block 2 the UB actually comes from the attempt to use `s` afterwards, not the memcpy itself. (And Block 3 actually doesn't have any use, so as you say, no UB yet). Will read that other thread first – M.M Jun 16 '15 at 00:06
  • Ah, yes, I missed the use in Block 2 (was looking at your excerpt, not the OP) – T.C. Jun 16 '15 at 00:09
  • @T.C. have updated now, taking that thread into account. I'm not sure about the variation on Block 1 where `t` is standard-layout, non-trivially-copyable, and an empty destructor. If we go with the interpretation that `memcpy` itself doesn't cause UB then it's hard to say where the downstream-UB would be in that case. (OTOH if we say it's well-defined then we just copied a non-trivially-copyable object, which isn't supposed to happen!) – M.M Jun 16 '15 at 00:35
  • I think this shows that we need more than just N3751 to sort out object lifetime issues – M.M Jun 16 '15 at 00:43