memset () oder Wertinitialisierung, um eine Struktur auf Null zu setzen?

76

In der Win32-API-Programmierung wird normalerweise C structs mit mehreren Feldern verwendet. Normalerweise haben nur einige von ihnen aussagekräftige Werte und alle anderen müssen auf Null gesetzt werden. Dies kann auf zwei Arten erreicht werden:

STRUCT theStruct;
memset( &theStruct, 0, sizeof( STRUCT ) );

oder

STRUCT theStruct = {};

Die zweite Variante sieht sauberer aus - sie ist ein Einzeiler, hat keine Parameter, die falsch eingegeben werden könnten und zu einem Fehler beim Pflanzen führen könnten.

Hat es irgendwelche Nachteile gegenüber der ersten Variante? Welche Variante und warum?

scharfer Zahn
quelle
Diese Antwort auf eine spätere Frage [1] scheint nützlicher und einfacher zu sein. [1]: stackoverflow.com/questions/4625212/class-initialization-list/…
TheMatto

Antworten:

94

Diese beiden Konstrukte haben eine sehr unterschiedliche Bedeutung. Die erste verwendet eine memsetFunktion, mit der ein Speicherpuffer auf einen bestimmten Wert gesetzt werden soll . Der zweite, der ein Objekt initialisiert . Lassen Sie es mich mit ein bisschen Code erklären:

Nehmen wir an, Sie haben eine Struktur, die nur Mitglieder von POD-Typen enthält ("Einfache alte Daten" - siehe Was sind POD-Typen in C ++? )

struct POD_OnlyStruct
{
    int a;
    char b;
};

POD_OnlyStruct t = {};  // OK

POD_OnlyStruct t;
memset(&t, 0, sizeof t);  // OK as well

In diesem Fall macht das Schreiben eines POD_OnlyStruct t = {}oder POD_OnlyStruct t; memset(&t, 0, sizeof t)keinen großen Unterschied, da der einzige Unterschied, den wir hier haben, darin besteht, dass die Ausrichtungsbytes im Falle der memsetVerwendung auf den Wert Null gesetzt werden. Da Sie normalerweise keinen Zugriff auf diese Bytes haben, gibt es für Sie keinen Unterschied.

Da Sie Ihre Frage als C ++ markiert haben, versuchen wir ein anderes Beispiel mit anderen Mitgliedstypen als POD :

struct TestStruct
{
    int a;
    std::string b;
};

TestStruct t = {};  // OK

{
    TestStruct t1;
    memset(&t1, 0, sizeof t1);  // ruins member 'b' of our struct
}  // Application crashes here

In diesem Fall ist die Verwendung eines Ausdrucks wie TestStruct t = {}gut, und die Verwendung eines memseton führt zum Absturz. Folgendes passiert, wenn Sie Folgendes verwenden memset: Es wird ein Objekt vom Typ TestStructerstellt, wodurch ein Objekt vom Typ erstellt wird std::string, da es Mitglied unserer Struktur ist. Als nächstes wird memsetder Speicher, in dem sich das Objekt bbefand, auf einen bestimmten Wert gesetzt, z. B. Null. Sobald unser TestStruct-Objekt den Gültigkeitsbereich verlässt, wird es zerstört und wenn der Zug zu seinem Mitglied kommt std::string b, wird ein Absturz angezeigt , da alle internen Strukturen dieses Objekts durch das zerstört wurden memset.

Die Realität ist also, dass diese Dinge sehr unterschiedlich sind , und obwohl Sie memsetin bestimmten Fällen manchmal eine ganze Struktur auf Null setzen müssen, ist es immer wichtig sicherzustellen, dass Sie verstehen, was Sie tun, und keinen Fehler wie in unserem zweiten machen Beispiel.

Meine Stimme - Einsatz memsetauf Objekte nur , wenn es erforderlich ist, und verwenden Sie die Standard - Initialisierung x = {}in allen anderen Fällen.

Dmitry
quelle
Hallo Dimity! Ich habe eine Struktur mit einigen Mitgliedern und habe die erste Option zum Memsetting ausprobiert: "struct stVar = {}". Aber ich bekomme die Warnung "-Wmissing-Field-Initialisierer". Ist es ein Problem?
MayurK
1
Mit POD meinen Sie in diesem Fall tatsächlich ein trivial konstruierbares Objekt (dh ein Objekt ohne vom Benutzer bereitgestellten C-Tor)? Ich denke nicht, dass es auf POD beschränkt sein sollte.
Al.G.
Dies wird nicht abstürzen: coliru.stacked-crooked.com/a/4b3dbf0b8761bc9b Es ist technisch undefiniertes Verhalten, da die Struktur nicht trivial zuweisbar ist (daher die Compiler-Warnung). Ich bezweifle jedoch, dass es eine gemeinsame Plattform gibt, für die Nullbytes ein ungültiger Wert sind std::string.
Kyle Strand
Ich denke, diese Antwort ist veraltet. In C ++ 11 werden Auffüllbits garantiert mit Null initialisiert:if T is a (possibly cv-qualified) non-union class type, each non-static data member and each base-class subobject is zero-initialized and padding is initialized to zero bits;
Clément
29

Abhängig von den Strukturelementen sind die beiden Varianten nicht unbedingt gleichwertig. memsetsetzt die Struktur auf all-bits-zero, während die Wertinitialisierung alle Mitglieder auf den Wert null initialisiert. Der C-Standard garantiert, dass diese nur für integrale Typen gleich sind, nicht für Gleitkommawerte oder Zeiger.

Außerdem erfordern einige APIs, dass die Struktur wirklich auf All-Bit-Null gesetzt wird. Beispielsweise verwendet die Berkeley-Socket-API Strukturen polymorph, und dort ist es wichtig, die gesamte Struktur wirklich auf Null zu setzen, nicht nur die Werte, die offensichtlich sind. In der API-Dokumentation sollte angegeben werden, ob die Struktur wirklich alle Bits Null sein muss, sie kann jedoch fehlerhaft sein.

Aber wenn keiner dieser oder ein ähnlicher Fall zutrifft, liegt es an Ihnen. Ich würde bei der Definition der Struktur eine Wertinitialisierung bevorzugen, da dies die Absicht klarer kommuniziert. Wenn Sie eine vorhandene Struktur memsetauf Null setzen müssen, ist dies natürlich die einzige Wahl (abgesehen von der Initialisierung jedes Elements auf Null von Hand, aber dies würde normalerweise nicht durchgeführt, insbesondere bei großen Strukturen).

JaakkoK
quelle
Aus Neugier ist auf welcher Plattform ein Float mit allen Bits zu Nullen nicht die positive Null?
Gregory Pakosz
3
Einige alte Pre-IEEE-754-CPUs hatten seltsame Float-Nullen. Nicht-754-Mathematik könnte noch zurückkommen, man weiß es nie, also ist es besser, diese Fehler nicht zu schreiben.
Andrew McGregor
1
Ist egal. Der C-Standard gibt nicht an, welches Float-Format verwendet wird. Selbst wenn es jetzt für IEEE 754 funktioniert, funktioniert es möglicherweise nicht für eine andere Float-Implementierung (Zukunft oder Vergangenheit)
Toad
3
Ich würde heutzutage nicht viele erraten, da IEEE so verbreitet ist, aber sie waren früher häufiger. Ich verstehe, dass Software-FP-Implementierungen typische Beispiele sind, bei denen Null nicht All-Bit-Null war. Sie werden also wahrscheinlich keine Probleme bekommen, aber dennoch schreibt C kein IEEE vor. Wenn also die Nullinitialisierung kein Engpass ist, kostet der "sicherere" Weg nichts wirklich.
JaakkoK
1
Wenn Sie jedes Mitglied auf Null initialisieren, wird nicht jedes Mitglied auf Null gesetzt, aber Sie werden die Füllbytes vermissen. Daher ist memset Ihre einzige Wahl.
Fmuecke
11

Wenn Ihre Struktur Dinge enthält wie:

int a;
char b;
int c;

Dann werden Auffüllbytes zwischen "b" und "c" eingefügt. memset () setzt diese auf Null, die andere nicht, sodass 3 Byte Müll vorhanden sind (wenn Ihre Ints 32 Bit sind). Wenn Sie beabsichtigen, Ihre Struktur zum Lesen / Schreiben aus einer Datei zu verwenden, kann dies wichtig sein.

Bobflux
quelle
2
Dies scheint nicht wahr zu sein. Aus CppReference: "Wenn T ein Nicht-Vereinigungsklassentyp ist, werden alle Basisklassen und nicht statischen Datenelemente mit Null initialisiert, und alle Auffüllungen werden mit Null Bits initialisiert. Die Konstruktoren, falls vorhanden, werden ignoriert." en.cppreference.com/w/cpp/language/zero_initialization
Kyle Strand
Gilt wahrscheinlich nur für C und nicht für C ++.
Syockit
7

Ich würde die Wertinitialisierung verwenden, da sie sauber und weniger fehleranfällig aussieht, wie Sie erwähnt haben. Ich sehe keinen Nachteil darin.

Sie können sich jedoch darauf verlassen memset, die Struktur auf Null zu setzen, nachdem sie verwendet wurde.

Gregory Pakosz
quelle
6

nicht, dass es üblich ist, aber ich denke, der zweite Weg hat auch den Vorteil, Floats auf Null zu initialisieren. Während ein Memset würde sicherlich nicht

Kröte
quelle
while doing a memset would certainly not- nicht ganz richtig. Bei x86- und x64-Mems wird ein Float / Double auf Null gesetzt und auf Null gesetzt. Sicher, dies ist nicht im C / C ++ - Standard enthalten, funktioniert aber auf den beliebtesten Plattformen.
sbk
2
sbk: fürs Erste ... wer weiß, welche Gleitkomma-Implementierung sie möglicherweise verwenden. IEEE 754 ist für den Compiler nicht definiert. Selbst wenn es jetzt funktioniert, ist es nur ein Glück für Sie, kann aber später zu Problemen führen.
Kröte
4

Die Wertinitialisierung, da sie zur Kompilierungszeit durchgeführt werden kann.
Außerdem initialisiert 0 alle POD-Typen korrekt.

Das memset () wird zur Laufzeit erstellt.
Auch die Verwendung von memset () ist verdächtig, wenn die Struktur nicht POD ist.
Initialisiert Nicht-Int-Typen nicht korrekt (auf Null).

Martin York
quelle
3
Die Werte werden beim Kompilieren nicht initialisiert. Der Compiler generiert einen Startcode, der alle Globals während des Programmstarts und damit zur Laufzeit initialisiert. Bei Stapelvariablen erfolgt die Initialisierung bei der Funktionseingabe - erneut zur Laufzeit.
Qrdl
@qrdl, hängt vom Compiler und Ziel ab. Für ROM-fähigen Code werden Werte manchmal zur Kompilierungszeit festgelegt.
Prof. Falken
2
@qrdl: Lassen Sie mich das umformulieren. Durch die Wertinitialisierung kann der Compiler (unter bestimmten Umständen) die Initialisierung zur Kompilierungszeit (und nicht zur Laufzeit) durchführen. Daher können zur Kompilierungszeit nur POD-Globals initialisiert werden.
Martin York
@qrdl: Wenn auf vielen Plattformen "foo" ein Int32_t der statischen Speicherklasse ist, wird die Laufzeitanweisung "foo = 0x12345678;" generiert Code zum Speichern von 0x12345678 in foo; Dieser Code wäre wahrscheinlich mindestens zehn Bytes lang, einige Mikrocontroller würden bis zu 32 Bytes benötigen. Eine Deklaration "Int32_t foo = 0x12345678;" würde auf vielen Plattformen dazu führen, dass die Variable in einem initialisierten Datensegment verknüpft wird und 4 Bytes zu einer Initialisierungsliste hinzugefügt werden. Auf einigen Systemen "Int32_t foo;" wäre vier Bytes billiger als "Int32_t foo = 0;", wobei letzteres foo in das initialisierte Datensegment zwingt.
Supercat
3

In einigen Compilern STRUCT theStruct = {};würde memset( &theStruct, 0, sizeof( STRUCT ) );in die ausführbare Datei übersetzt. Einige C-Funktionen sind bereits für die Laufzeiteinrichtung verknüpft, sodass der Compiler diese Bibliotheksfunktionen wie memset / memcpy zur Verfügung hat.

Gerhard
quelle
2
Das hat mich in letzter Zeit wirklich hart gebissen. Ich arbeitete an einem benutzerdefinierten Komprimierungscode und initialisierte einige große Strukturen zur Deklarationszeit mit struct something foo = { x, y, z }und Cachegrind zeigte, dass 70% der "Arbeit" meines Programms ausgeführt wurde, memsetda die Strukturen bei JEDEM Funktionsaufruf auf Null gesetzt wurden.
Jody Bruchon
-1

Wenn es viele Zeigerelemente gibt und Sie wahrscheinlich in Zukunft weitere hinzufügen werden, kann es hilfreich sein, memset zu verwenden. In Kombination mit geeigneten assert(struct->member)Aufrufen können Sie verhindern, dass zufällige Abstürze versuchen, einen fehlerhaften Zeiger zu erkennen, den Sie vergessen haben, zu initialisieren. Aber wenn Sie nicht so vergesslich sind wie ich, ist die Initialisierung der Mitglieder wahrscheinlich die beste!

Allerdings , wenn Sie Ihre Struktur als Teil einer öffentlichen API verwendet wird, sollten Sie Client - Code zu verwenden Memset als Anforderung erhalten. Dies hilft bei der Zukunftssicherung, da Sie neue Mitglieder hinzufügen können und der Client-Code diese im Memset-Aufruf automatisch NULL macht, anstatt sie in einem (möglicherweise gefährlichen) nicht initialisierten Zustand zu belassen. Dies tun Sie beispielsweise, wenn Sie mit Socket-Strukturen arbeiten.

Mike Weller
quelle
Wie hilft es bei der Zukunftssicherung? Wenn Sie davon ausgehen, dass der Clientcode nicht neu kompiliert wird, wird er memsetmit der falschen Strukturgröße aufgerufen. Wenn der Clientcode neu kompiliert wird, muss er auf die aktualisierte Headerdatei mit der Strukturdefinition zugreifen, damit die memsetInitialisierung entweder oder der Wert funktioniert. (Der Client und die Bibliothek müssen jedoch eine konsistente Vorstellung davon haben, wie der Nullzeiger dargestellt wird. Wenn die API dies empfiehlt memset, sollte sie gegen alle Bits Null und nicht gegen NULL
prüfen
Wenn die Struktur Teil einer öffentlichen API ist, sollte man möglicherweise stattdessen eine undurchsichtige Struktur mit einer Initialisierungsfunktion in Betracht ziehen.
Jamesdlin