Was bedeutet {0} beim Initialisieren eines Objekts?

252

Wenn {0}verwendet wird , um ein Objekt zu initialisieren, was bedeutet es? Ich kann {0}nirgendwo Verweise finden und wegen der geschweiften Klammern ist die Google-Suche nicht hilfreich.

Beispielcode:

SHELLEXECUTEINFO sexi = {0}; // what does this do?
sexi.cbSize = sizeof(SHELLEXECUTEINFO);
sexi.hwnd = NULL;
sexi.fMask = SEE_MASK_NOCLOSEPROCESS;
sexi.lpFile = lpFile.c_str();
sexi.lpParameters = args;
sexi.nShow = nShow;

if(ShellExecuteEx(&sexi))
{
    DWORD wait = WaitForSingleObject(sexi.hProcess, INFINITE);
    if(wait == WAIT_OBJECT_0)
        GetExitCodeProcess(sexi.hProcess, &returnCode);
}

Ohne diesen Absturz stürzt der obige Code zur Laufzeit ab.

Mahmoud Al-Qudsi
quelle

Antworten:

302

Was hier passiert, wird als aggregierte Initialisierung bezeichnet. Hier ist die (abgekürzte) Definition eines Aggregats aus Abschnitt 8.5.1 der ISO-Spezifikation:

Ein Aggregat ist ein Array oder eine Klasse ohne vom Benutzer deklarierte Konstruktoren, ohne private oder geschützte nicht statische Datenelemente, ohne Basisklassen und ohne virtuelle Funktionen.

Die {0}Initialisierung eines 0solchen Aggregats ist im Grunde genommen ein Trick für die gesamte Sache. Dies liegt daran, dass Sie bei Verwendung der Aggregatinitialisierung nicht alle Mitglieder angeben müssen und die Spezifikation erfordert, dass alle nicht angegebenen Mitglieder standardmäßig initialisiert werden, was bedeutet, dass sie 0für einfache Typen festgelegt sind.

Hier ist das relevante Zitat aus der Spezifikation:

Wenn die Liste weniger Initialisierer enthält als Mitglieder im Aggregat, wird jedes nicht explizit initialisierte Mitglied standardmäßig initialisiert. Beispiel:

struct S { int a; char* b; int c; };
S ss = { 1, "asdf" };

initialisiert ss.amit 1, ss.bmit "asdf"und ss.cmit dem Wert eines Ausdrucks der Form int(), d. h 0.

Die vollständige Spezifikation zu diesem Thema finden Sie hier

Don Neufeld
quelle
15
Hervorragende Resonanz. Ich wollte nur hinzufügen, dass das Initialisieren eines Aggregats mit {0} dasselbe ist wie das Initialisieren mit einfach {}. Vielleicht macht Ersteres deutlicher, dass eingebaute Typen auf Null gesetzt werden.
James Hopkin
7
Einige Compiler verschlucken sich an {}, deshalb wird {0} verwendet
Branan
10
Nicht ganz. Wenn in C ++ das erste Mitglied nicht nullkonstruiert werden kann, funktioniert {0} nicht. Beispiel: Struktur A {B b; int i; char c; }; Struktur B {B (); B (Zeichenfolge); }; A a = {}; // Diese Anweisung kann nicht als 'A a = {0}' umgeschrieben werden.
Aaron
25
@Branan, das liegt daran, dass in C "{}" nicht gültig ist. In C ++ ist es. @ don.neufeld, dies hat sich mit C ++ 03 geändert (standardmäßig initialisiert -> wertinitialisiert). Das Zitat zitiert den C ++ 98 Standard.
Johannes Schaub - litb
3
Ersetzt dies die Notwendigkeit, ZeroMemory () (in VC ++) zu verwenden?
Ray
89

Eine Sache, die Sie beachten sollten, ist, dass diese Technik die Füllbytes nicht auf Null setzt. Beispielsweise:

struct foo
{
    char c;
    int  i;
};

foo a = {0};

Ist nicht dasselbe wie:

foo a;
memset(&a,0,sizeof(a));

Im ersten Fall werden Pad-Bytes zwischen c und i nicht initialisiert. Warum sollte es dich interessieren? Wenn Sie diese Daten auf der Festplatte speichern oder über ein Netzwerk oder was auch immer senden, liegt möglicherweise ein Sicherheitsproblem vor.

Harold Ekstrom
quelle
13
Natürlich ist es nur dann ein Sicherheitsproblem, wenn Sie "schreiben (f, & a, sizeof (a))", wodurch auf verschiedenen Prozessoren / Compilern unterschiedliche Dateicodierungen erzeugt werden können. Eine gut formatierte Ausgabe wäre ohne das Memset sicher.
Aaron
3
Wenn Sie Inhalte über das Netzwerk senden, stellen Sie außerdem immer die Ausrichtung ein, die gepackt werden soll. Auf diese Weise erhalten Sie so wenig zusätzliche Füllbytes wie möglich.
Mark Kegel
18
Es sollte beachtet werden, dass, obwohl die Spezifikation nicht erfordert, dass das Padding initialisiert wird, jeder vernünftige Compiler dies tut, da es nur Zeit kostet, "um" herum zu initialisieren.
Tomas
4
Ich möchte Probleme mit der Verwendung der Wörter "ist nicht" haben. Auffüllbytes sind implementierungsdefiniert. Dem Compiler steht es frei, foo a = {0} nach Belieben in ein Memset (& a, 0, sizeof (a)) umzuwandeln. Es ist nicht erforderlich, Füllbytes zu "überspringen" und nur foo.c und foo.i zu setzen. +1 für den (potenziellen) Sicherheitsfehler tho
SecurityMatt
3
@Leushenko Punkt 19 sagt "alle Unterobjekte, die nicht explizit initialisiert werden", also würde ich mich auf Punkt 21 (den Sie zitiert haben) stützen, der schlampig ist. Es wäre wirklich bizarr, wenn die Spezifikation zulassen würde, dass das Auffüllen bis zum Moment des letzten Initialisierers nicht initialisiert wird. Danach musste das Auffüllen auf Null gesetzt werden, insbesondere wenn man bedenkt, dass Initialisierer möglicherweise nicht in der richtigen Reihenfolge erscheinen, wenn bestimmte Initialisierer verwendet werden.
MM
20

Beachten Sie, dass ein leerer Aggregatinitialisierer auch funktioniert:

SHELLEXECUTEINFO sexi = {};
char mytext[100] = {};
dalle
quelle
11

Als Antwort darauf, warum ShellExecuteEx()es abstürzt: Ihre SHELLEXECUTEINFO"sexi" -Struktur hat viele Mitglieder und Sie initialisieren nur einige von ihnen.

Das Mitglied sexi.lpDirectorykönnte beispielsweise auf eine beliebige Stelle zeigen, versucht jedoch ShellExecuteEx()weiterhin, sie zu verwenden, sodass eine Speicherzugriffsverletzung auftritt.

Wenn Sie die Zeile einfügen:

SHELLEXECUTEINFO sexi = {0};

Vor dem Rest Ihrer Struktureinrichtung weisen Sie den Compiler an, alle Strukturelemente auf Null zu setzen, bevor Sie die spezifischen Elemente initialisieren, an denen Sie interessiert sind. ShellExecuteEx()Wenn sexi.lpDirectoryNull ist, sollte er diese ignorieren.

snowcrash09
quelle
7

Ich benutze es auch, um Strings zu initialisieren, z.

char mytext[100] = {0};
Adam Pierce
quelle
5
Wenn mytext natürlich als Zeichenfolge verwendet wird, char mytext [100]; mytext [0] = '\ 0'; Dies hätte den gleichen Effekt wie die Angabe einer leeren Zeichenfolge, würde jedoch nur bewirken, dass die Implementierung das erste Byte auf Null setzt.
Chris Young
@ Chris: Ich habe mir oft gewünscht, es gäbe eine Syntax für die teilweise Objektinitialisierung. In der Lage zu sein, x zu "deklarieren und zu initialisieren", dann ebenfalls mit y, dann z, ist viel schöner, als x, y und z deklarieren und dann x, y und z initialisieren zu müssen, aber nur dann 100 Bytes zu initialisieren man braucht tatsächlich eine Initialisierung scheint eher verschwenderisch.
Supercat
7

{0}ist ein gültiger Initialisierer für jeden (vollständigen Objekt-) Typ in C und C ++. Es ist eine gebräuchliche Redewendung, mit der ein Objekt auf Null initialisiert wird (lesen Sie weiter, um zu sehen, was das bedeutet).

Für Skalartypen (Arithmetik- und Zeigertypen) sind die geschweiften Klammern nicht erforderlich, aber ausdrücklich zulässig. Zitieren des N1570-Entwurfs der ISO C-Norm, Abschnitt 6.7.9:

Der Initialisierer für einen Skalar muss ein einzelner Ausdruck sein, der optional in geschweiften Klammern eingeschlossen ist.

Es initialisiert das Objekt auf Null ( 0für Ganzzahlen, 0.0für Gleitkomma, einen Nullzeiger für Zeiger).

Gibt für nicht skalare Typen (Strukturen, Arrays, Vereinigungen) an, {0}dass das erste Element des Objekts auf Null initialisiert wird. Bei Strukturen, die Strukturen, Arrays von Strukturen usw. enthalten, wird dies rekursiv angewendet, sodass das erste Skalarelement je nach Typ auf Null gesetzt wird. Wie bei jedem Initialisierer werden alle nicht angegebenen Elemente auf Null gesetzt.

Zwischenklammern ( {, }) können weggelassen werden; Zum Beispiel sind beide gültig und gleichwertig:

int arr[2][2] = { { 1, 2 }, {3, 4} };

int arr[2][2] = { 1, 2, 3, 4 };

Deshalb müssen Sie beispielsweise nicht { { 0 } }für einen Typ schreiben, dessen erstes Element nicht skalar ist.

Also das:

some_type obj = { 0 };

ist eine Kurzform der Initialisierung objauf Null, was bedeutet, dass jedes skalare Unterobjekt von auf objgesetzt wird, 0wenn es eine Ganzzahl ist, 0.0wenn es Gleitkomma ist, oder auf einen Nullzeiger, wenn es ein Zeiger ist.

Die Regeln für C ++ sind ähnlich.

In Ihrem speziellen Fall ist sexi.cbSizees klar, dass SHELLEXECUTEINFOes sich um einen Struktur- oder Klassentyp handelt (oder möglicherweise um eine Vereinigung, aber wahrscheinlich nicht) , da Sie Werte usw. zuweisen. Dies gilt also nicht für alles, aber wie gesagt, { 0 }ist es üblich Redewendung, die in allgemeineren Situationen verwendet werden kann.

Dies ist nicht (notwendigerweise) gleichbedeutend mit der Verwendung memset, um die Darstellung des Objekts auf alle Bits Null zu setzen. Weder Gleitkomma 0.0noch ein Nullzeiger werden notwendigerweise als All-Bit-Null dargestellt, und ein { 0 }Initialisierer setzt nicht notwendigerweise Füllbytes auf einen bestimmten Wert. Auf den meisten Systemen hat dies jedoch wahrscheinlich den gleichen Effekt.

Keith Thompson
quelle
1
Ist in C ++ {0}kein gültiger Initialisierer für ein Objekt, das kein Konstruktor akzeptiert 0. noch für ein Aggregat, dessen erstes Element als solches ist (oder ein Aggregat ohne Elemente)
MM
3

Es ist schon eine Weile her, dass ich in c / c ++ gearbeitet habe, aber IIRC, dieselbe Verknüpfung kann auch für Arrays verwendet werden.

µBio
quelle
2

Ich habe mich immer gefragt, warum Sie so etwas verwenden sollten

struct foo bar = { 0 };

Hier ist ein Testfall zu erklären:

check.c

struct f {
    int x;
    char a;
} my_zero_struct;

int main(void)
{
    return my_zero_struct.x;
}

Ich kompiliere mit gcc -O2 -o check check.cund readelf -s check | sort -k 2gebe dann die Symboltabelle mit aus (dies ist mit gcc 4.6.3 unter Ubuntu 12.04.2 auf einem x64-System). Auszug:

59: 0000000000601018     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_start
48: 0000000000601018     0 NOTYPE  GLOBAL DEFAULT  ABS _edata
25: 0000000000601018     0 SECTION LOCAL  DEFAULT   25 
33: 0000000000601018     1 OBJECT  LOCAL  DEFAULT   25 completed.6531
34: 0000000000601020     8 OBJECT  LOCAL  DEFAULT   25 dtor_idx.6533
62: 0000000000601028     8 OBJECT  GLOBAL DEFAULT   25 my_zero_struct
57: 0000000000601030     0 NOTYPE  GLOBAL DEFAULT  ABS _end

Der wichtige Teil hier ist, das my_zero_structist danach __bss_start. Der Abschnitt ".bss" in einem C-Programm ist der Speicherabschnitt, der auf Null gesetzt wurde, bevor er main aufgerufen wird (siehe Wikipedia auf .bss) .

Wenn Sie den obigen Code ändern in:

} my_zero_struct = { 0 };

Dann wird die resultierende „check“ ausführbare sieht genau die gleiche zumindest mit den gcc 4.6.3 Compiler auf Ubuntu 12.04.2; Das my_zero_structbefindet sich noch im .bssAbschnitt und wird daher automatisch auf Null initialisiert, bevor maines aufgerufen wird.

Hinweise in den Kommentaren, dass a memsetmöglicherweise die "vollständige" Struktur initialisiert, sind ebenfalls keine Verbesserung, da der .bssAbschnitt vollständig gelöscht wird, was auch bedeutet, dass die "vollständige" Struktur auf Null gesetzt ist.

Es kann sein, dass der C-Sprachstandard nichts davon erwähnt, aber in einem realen C-Compiler habe ich noch nie ein anderes Verhalten gesehen.

Ingo Blackman
quelle
Globale und statische Variablen werden immer standardmäßig auf 0 oder Standard-Ctor initialisiert. Wenn Sie jedoch die Instanz von f lokal deklarieren, erhalten Sie möglicherweise unterschiedliche Ergebnisse.
Logman
0

Es ist syntatischer Zucker, um Ihre gesamte Struktur auf leere / Null / Null-Werte zu initialisieren.

Lange Version

SHELLEXECUTEINFO sexi;
sexi.cbSize = 0;
sexi.fMask = 0;
sexi.hwnd = NULL;
sexi.lpVerb = NULL;
sexi.lpFile = NULL;
sexi.lpParameters = NULL;
sexi.lpDirectory = NULL;
sexi.nShow = nShow;
sexi.hInstApp = 0;
sexi.lpIDList = NULL;
sexi.lpClass = NULL;
sexi.hkeyClass = 0;
sexi.dwHotKey = 0;
sexi.hMonitor = 0;
sexi.hProcess = 0;

Kurzfassung

SHELLEXECUTEINFO sexi = {0};

War das nicht viel einfacher?

Es ist auch schön, weil:

  • Sie müssen nicht jedes Mitglied suchen und initialisieren
  • Sie müssen sich keine Sorgen machen, dass Sie neue Mitglieder möglicherweise nicht initialisieren, wenn sie später hinzugefügt werden
  • Sie müssen nicht anrufenZeroMemory
Ian Boyd
quelle
-5

{0} ist ein anonymes Array , dessen Element 0 ist.

Dies wird verwendet, um ein oder alle Elemente des Arrays mit 0 zu initialisieren.

zB int arr [8] = {0};

In diesem Fall werden alle Elemente von arr als 0 initialisiert.

Nitin Prajapati
quelle
4
{0}ist kein anonymes Array. Es ist nicht einmal ein Ausdruck. Es ist ein Initialisierer.
Keith Thompson