2015-06-15 19 views
5

std :: aligned_storage :: type è il tipo POD. Il tipo di POD può memcpy. Tuttavia, cosa succede se il posizionamento di un nuovo tipo non banale-copyable in std :: aligned_storage? Può memcpy che std :: aligned_storage?può memcpy per std :: aligned_storage?

tipo non banale-copiabile (tipo non POD) NON può memcpy, Comportamento non definito. Se std :: aligned_storage memcpy non è un tipo non banale, è anche un comportamento indefinito?

#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???? 
    } 
} 

Ho pensato che anche questo non è definito. Tuttavia, abbiamo lavoro. È per coincidenza?

+0

'memcpy()' prende gli indirizzi di memoria, incurante se questi sono allineati o meno. –

+0

Ciao. Le [linee guida per la pubblicazione] (http://stackoverflow.com/help/how-to-ask) su SO sono che il codice dovrebbe apparire nel post e dovrebbe essere * Minimal * per mostrare il problema. Ho trascritto il tuo codice e l'ho semplificato un po ', spero che mostri ancora lo stesso comportamento per te. –

+0

@MattMcNabb Grazie – Cocoa

risposta

7

Prima di tutto: come discusso in this thread, lo standard C++ definisce solo il comportamento di memcpy per oggetti banalmente copiabili. Quel thread fornisce un esempio specifico di come può essere interrotto per le opzioni non banalmente copiabili

Quindi, un'interpretazione ristretta dello standard direbbe che il semplice atto di chiamare memcpy causa UB.

Tuttavia, un'interpretazione più comune sarebbe che è OK copiare i byte, ma qualsiasi tentativo di trattare il target come effettivamente contenente un oggetto dello stesso tipo causerebbe UB. Soprattutto nel caso in cui il programma dipenda dagli effetti collaterali del distruttore, come fa il tuo. Il resto della mia risposta si basa su quest'ultima interpretazione.


A partire con il blocco 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 

Dal t non è banalmente copiabile, non abbiamo creato un oggetto t valido s s' archiviazione. Quindi il tentativo di utilizzare s come se contenesse un oggetto valido t causa certamente un comportamento non definito. Quando UB si verifica, è possibile seguire qualsiasi risultato, incluso (ma non limitato a) che sembra "funzionare come previsto".


nel blocco 1 (se l'memcpy sia commentata):

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

Qui abbiamo destructor effetti collaterali. Lo memcpy termina la vita di a (poiché la memoria di a viene riutilizzata). Comunque il codice andrà avanti per provare a chiamare il distruttore su a.

In questo caso, anche se la copia "sembra funzionare", l'interruzione viene probabilmente dal doppio libero di a.p.

Se abbiamo modificato t come un tipo non banale-copiabile ma senza effetti collaterali del distruttore, questo esempio non sarebbe chiaro.

Non esiste tale doppio libero nel Blocco 2 poiché nessun distruttore viene mai invocato per lo memorizzato in s.


Blocco 3:

Questo è simile al blocco 2: il memcpy non crea un oggetto."Sembra funzionare" perché non invochi mai i distruttori per gli oggetti in align_storage.

Infatti, in base all'interpretazione del senso comune di memcpy, non c'è UB qui perché non si è tentato di utilizzare il risultato della copia dei byte e la destinazione non ha un distruttore chiamato su di esso. (Se hai copiato la tua riga cout qui, causerebbe UB per la stessa ragione del Blocco 2).


discussione correlati: Anche per le classi banalmente copiabili si è still murky

Lo standard C++ è chiaro attorno alle questioni di cui oggetto vita inizia per gli oggetti in malloc 'spazio d o aligned_storage. C'era una sottomissione N3751 riconoscendo che questo ha bisogno di pulizia, ma c'è ancora molto lavoro da fare.

Nel blocco 2, la durata non è iniziata per s. Questo perché t ha un'inizializzazione non banale. (Questo in realtà non è chiaramente indicato dallo standard C++). Tuttavia il blocco 1 a è un oggetto la cui durata è iniziata.

N3751 propone che (se t fosse banalmente copiabile), quindi il memcpy inizierebbe effettivamente la durata di s.

+0

Non c'è stato un lungo dibattito su SO di recente se "memcpy'ing i byte di un tipo non banale-copyable è UB? –

+0

@ T.C. copia * di *? Sicuramente no. Non ho visto nessun thread recente diverso da quello collegato nella mia risposta. –

+0

[Fun dibattito] (http://stackoverflow.com/questions/29777492/why-would-the-behavior-of-stdmemcpy-be-undefined-for-objects-that-are- no -triv). Non penso che sia Block 2 o Block 3 abbia UB (in entrambi i casi, si copia un mucchio di byte di un 'T' in un archivio, che va bene). –