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
.
'memcpy()' prende gli indirizzi di memoria, incurante se questi sono allineati o meno. –
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. –
@MattMcNabb Grazie – Cocoa