Ist es gültig, eine Struktur zu kopieren, deren Mitglieder nicht initialisiert sind?
Ich vermute, dass es sich um ein undefiniertes Verhalten handelt, aber wenn dies der Fall ist, ist es sehr gefährlich, nicht initialisierte Mitglieder in einer Struktur zu belassen (auch wenn diese Mitglieder niemals direkt verwendet werden). Ich frage mich also, ob der Standard etwas enthält, das dies zulässt.
Ist das zum Beispiel gültig?
struct Data {
int a, b;
};
int main() {
Data data;
data.a = 5;
Data data2 = data;
}
c++
initialization
copy-constructor
undefined-behavior
Tomek Czajka
quelle
quelle
Antworten:
Ja, wenn das nicht initialisierte Element kein vorzeichenloser schmaler Zeichentyp ist oder das
std::byte
Kopieren einer Struktur, die diesen unbestimmten Wert enthält, mit dem implizit definierten Kopierkonstruktor ein technisch undefiniertes Verhalten ist, wie dies beim Kopieren einer Variablen mit einem unbestimmten Wert desselben Typs der Fall ist von [dcl.init] / 12 .Dies gilt hier, da der implizit generierte Kopierkonstruktor mit Ausnahme von
union
s so definiert ist, dass jedes Mitglied einzeln wie durch direkte Initialisierung kopiert wird , siehe [class.copy.ctor] / 4 .Dies ist auch Gegenstand der aktiven CWG-Ausgabe 2264 .
In der Praxis werden Sie damit allerdings kein Problem haben.
Wenn Sie 100% sicher sein möchten, hat die Verwendung
std::memcpy
immer ein genau definiertes Verhalten, wenn der Typ trivial kopierbar ist , auch wenn Mitglieder einen unbestimmten Wert haben.Abgesehen von diesen Problemen sollten Sie Ihre Klassenmitglieder bei der Erstellung ohnehin immer ordnungsgemäß mit einem bestimmten Wert initialisieren, vorausgesetzt, Sie benötigen für die Klasse keinen trivialen Standardkonstruktor . Sie können dies ganz einfach mit der Standard-Syntax für die Elementinitialisierung tun, um z. B. die Elemente mit einem Wert zu initialisieren:
quelle
memcpy
, selbst für trivial kopierbare Typen. Die einzige Ausnahme bilden Gewerkschaften, für die der implizite Kopierkonstruktor die Objektdarstellung wie von kopiertmemcpy
.Im Allgemeinen ist das Kopieren nicht initialisierter Daten ein undefiniertes Verhalten, da sich diese Daten möglicherweise in einem Überfüllungszustand befinden. Diese Seite zitieren :
Signalisierungs-NaNs sind für Gleitkommatypen und auf einigen Plattformen Ganzzahlen möglich können Trap - Darstellungen.
Für trivial kopierbare Typen ist es jedoch möglich,
memcpy
die Rohdarstellung des Objekts zu kopieren. Dies ist sicher, da der Wert des Objekts nicht interpretiert wird und stattdessen die Rohbyte-Sequenz der Objektdarstellung kopiert wird.quelle
unsigned char[64]
)? Das Behandeln der Bytes einer Struktur als nicht spezifizierte Werte könnte die Optimierung unnötig behindern, aber die Anforderung, dass Programmierer das Array manuell mit nutzlosen Werten füllen müssen, würde die Effizienz noch mehr beeinträchtigen.In einigen Fällen, wie dem beschriebenen, ermöglicht der C ++ - Standard Compilern, Konstrukte auf die Weise zu verarbeiten, die ihre Kunden am nützlichsten finden, ohne dass das Verhalten vorhersehbar sein muss. Mit anderen Worten, solche Konstrukte rufen "undefiniertes Verhalten" auf. Dies bedeutet jedoch nicht, dass solche Konstrukte "verboten" sein sollen, da der C ++ - Standard ausdrücklich auf die Zuständigkeit darüber verzichtet, was wohlgeformte Programme "dürfen". Obwohl mir kein veröffentlichtes Begründungsdokument für den C ++ - Standard bekannt ist, deutet die Tatsache, dass es undefiniertes Verhalten ähnlich wie C89 beschreibt, darauf hin, dass die beabsichtigte Bedeutung ähnlich ist: "Undefiniertes Verhalten gibt dem Implementierer die Lizenz, bestimmte Programmfehler, die schwierig sind, nicht abzufangen diagnostizieren.
Es gibt viele Situationen, in denen die effizienteste Art, etwas zu verarbeiten, darin besteht, die Teile einer Struktur zu schreiben, um die sich Downstream-Code kümmern wird, während diejenigen weggelassen werden, die Downstream-Code nicht interessieren. Die Forderung, dass Programme alle Mitglieder einer Struktur initialisieren, einschließlich derer, um die sich nichts kümmern wird, würde die Effizienz unnötig beeinträchtigen.
Darüber hinaus gibt es einige Situationen, in denen es möglicherweise am effizientesten ist, wenn sich nicht initialisierte Daten nicht deterministisch verhalten. Zum Beispiel gegeben:
Wenn sich der nachgelagerte Code nicht um die Werte von Elementen kümmert
x.dat
odery.dat
deren Indizes nicht aufgeführt sindarr
, kann der Code wie folgt optimiert werden:Diese Verbesserung der Effizienz wäre nicht möglich, wenn Programmierer
temp.dat
vor dem Kopieren jedes Element explizit schreiben müssten, einschließlich derjenigen, die sich nicht um die nachgelagerten Elemente kümmern würden.Andererseits gibt es einige Anwendungen, bei denen es wichtig ist, die Möglichkeit eines Datenverlusts zu vermeiden. In solchen Anwendungen kann es nützlich sein, entweder eine Version des Codes zu haben, die instrumentiert ist, um jeden Versuch abzufangen, nicht initialisierten Speicher zu kopieren, ohne zu berücksichtigen, ob nachgeschalteter Code ihn betrachten würde, oder es könnte nützlich sein, eine Implementierungsgarantie für jeden Speicher zu haben deren Inhalt durchgesickert sein könnte, würde auf Null gesetzt oder auf andere Weise mit nicht vertraulichen Daten überschrieben.
Nach allem, was ich sagen kann, unternimmt der C ++ - Standard keinen Versuch zu sagen, dass eines dieser Verhaltensweisen nützlicher als das andere ist, um eine Mandatierung zu rechtfertigen. Ironischerweise kann dieser Mangel an Spezifikation dazu dienen, die Optimierung zu erleichtern. Wenn Programmierer jedoch keine schwachen Verhaltensgarantien ausnutzen können, werden Optimierungen negiert.
quelle
Da alle Mitglieder von
Data
primitiven Typen sind,data2
erhalten sie eine exakte "bitweise Kopie" aller Mitglieder vondata
. Der Wert vondata2.b
ist also genau der gleiche wie der Wert vondata.b
. Der genaue Wert vondata.b
kann jedoch nicht vorhergesagt werden, da Sie ihn nicht explizit initialisiert haben. Dies hängt von den Werten der Bytes in dem Speicherbereich ab, der für die zugewiesen istdata
.quelle
std::memcpy
. Nichts davon verhindert die Verwendung vonstd::memcpy
oderstd::memmove
. Es wird nur die Verwendung des impliziten Kopierkonstruktors verhindert.