Cosa significa {0} quando si inizializza un oggetto?


252

Quando {0}viene utilizzato per inizializzare un oggetto, cosa significa? Non riesco a trovare riferimenti a {0}nessuna parte e, a causa delle parentesi graffe, le ricerche di Google non sono utili.

Codice di esempio:

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);
}

Senza di esso, il codice sopra si arresterà in modo anomalo in fase di esecuzione.

Risposte:


302

Quello che sta succedendo qui si chiama inizializzazione aggregata . Ecco la definizione (abbreviata) di un aggregato dalla sezione 8.5.1 della specifica ISO:

Un aggregato è un array o una classe senza costruttori dichiarati dall'utente, senza membri di dati non statici privati ​​o protetti, senza classi di base e senza funzioni virtuali.

Ora, usare {0}per inizializzare un aggregato come questo è fondamentalmente un trucco per 0l'intera cosa. Questo perché quando si utilizza l'inizializzazione aggregata non è necessario specificare tutti i membri e le specifiche richiedono che tutti i membri non specificati siano inizializzati per impostazione predefinita, il che significa che è impostato su 0per tipi semplici.

Ecco la citazione pertinente dalle specifiche:

Se nell'elenco sono presenti meno inizializzatori rispetto ai membri nell'aggregazione, ogni membro non inizializzato esplicitamente deve essere inizializzato per impostazione predefinita. Esempio:

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

si inizializza ss.acon 1, ss.bcon "asdf"e ss.ccon il valore di un'espressione della forma int(), ovvero 0.

Puoi trovare le specifiche complete su questo argomento qui


15
Risposta eccellente. Volevo solo aggiungere che l'inizializzazione di un aggregato con {0} equivale a inizializzarlo con semplicemente {}. Forse il primo rende più ovvio che i tipi predefiniti vengono azzerati.
James Hopkin,

7
Alcuni compilatori soffocano su {}, ecco perché viene utilizzato {0}
Branan,

10
Non proprio .. In C ++, se il primo membro non può essere costruito a zero, {0} non funzionerà. Esempio: struct A {B b; int i; char c; }; struct B {B (); B (stringa); }; A a = {}; // questa affermazione non può essere riscritta come 'A a = {0}'.
Aaron,

25
@Branan, questo perché in C "{}" non è valido. In C ++ lo è. @ don.neufeld, questo è cambiato con C ++ 03 (default-inizializzato -> valore-inizializzato). La citazione cita lo standard C ++ 98, tenere presente.
Johannes Schaub - litb

3
Quindi, questo sostituisce la necessità di utilizzare ZeroMemory () (in VC ++)?
Ray,

89

Una cosa da tenere presente è che questa tecnica non imposterà i byte di riempimento a zero. Per esempio:

struct foo
{
    char c;
    int  i;
};

foo a = {0};

Non è lo stesso di:

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

Nel primo caso, i byte pad tra c e i non sono inizializzati. Perché te ne importa? Bene, se stai salvando questi dati su disco o inviandoli su una rete o altro, potresti avere un problema di sicurezza.


13
Ovviamente, è solo un problema di sicurezza se 'scrivi (f, & a, sizeof (a))', che può produrre differenti codifiche di file su processori / compilatori diversi. Un output ben formattato sarebbe sicuro senza il memset.
Aaron,

3
Inoltre, se invii materiale attraverso la rete, imposterai sempre l'allineamento da impacchettare. In questo modo si ottengono il minor numero possibile di byte di riempimento extra.
Mark Kegel,

18
Va notato che sebbene le specifiche non richiedano l'inizializzazione dell'imbottitura, qualsiasi compilatore sano funzionerà, poiché costa solo tempo per inizializzarlo "attorno".
Tomas,

4
Vorrei contestare l'uso delle parole "non è". I byte di riempimento sono definiti dall'implementazione. Il compilatore è libero di trasformare foo a = {0} in memset (& a, 0, sizeof (a)) a suo piacimento. Non è necessario "saltare" i byte di riempimento e impostare solo foo.c e foo.i. +1 per il (potenziale) bug di sicurezza tho
SecurityMatt

3
Il punto 19 di @Leushenko dice "tutti gli oggetti secondari che non sono stati inizializzati in modo esplicito", quindi mi appoggiavo al punto 21 (che hai citato) essendo sciatto. Sarebbe davvero bizzarro se le specifiche consentissero di non inizializzare il padding fino al momento dell'ultimo inizializzatore, dopo di che il padding doveva essere azzerato, specialmente considerando che gli inizializzatori possono apparire fuori servizio quando si usano inizializzatori designati.
MM

20

Si noti che funziona anche un inizializzatore aggregato vuoto:

SHELLEXECUTEINFO sexi = {};
char mytext[100] = {};

11

In risposta al motivo per cui ShellExecuteEx()sta andando in crash: la tua struttura SHELLEXECUTEINFO"sexi" ha molti membri e ne stai inizializzando solo alcuni.

Ad esempio, il membro sexi.lpDirectorypotrebbe puntare ovunque, ma ShellExecuteEx()proverà comunque a usarlo, quindi otterrai una violazione di accesso alla memoria.

Quando includi la linea:

SHELLEXECUTEINFO sexi = {0};

prima del resto della configurazione della struttura, stai dicendo al compilatore di azzerare tutti i membri della struttura prima di inizializzare quelli specifici che ti interessano. ShellExecuteEx()sa che se sexi.lpDirectoryè zero, dovrebbe ignorarlo.


7

Lo uso anche per inizializzare stringhe ad es.

char mytext[100] = {0};

5
Tuttavia, ovviamente, se mytext viene utilizzato come stringa, char mytext [100]; mytext [0] = '\ 0'; avrebbe lo stesso effetto di dare una stringa vuota, ma farebbe sì che l'implementazione azzerasse il primo byte.
Chris Young,

@ Chris: Ho spesso desiderato che ci fosse una sintassi per l'inizializzazione parziale dell'oggetto. Essere in grado di "dichiarare e inizializzare" x, quindi fare altrettanto con y, quindi z, è molto più bello che dover dichiarare x, ye z, quindi inizializzare x, ye z, ma inizializzando 100 byte quando solo uno effettivamente ha bisogno di inizializzazione sembra piuttosto dispendioso.
supercat

7

{0}è un inizializzatore valido per qualsiasi tipo (oggetto completo), sia in C che in C ++. È un linguaggio comune usato per inizializzare un oggetto a zero (continuate a leggere per vedere cosa significa).

Per i tipi scalari (tipi aritmetici e puntatori), le parentesi graffe non sono necessarie, ma sono esplicitamente consentite. Citando la bozza N1570 dello standard ISO C, sezione 6.7.9:

L'inizializzatore per uno scalare deve essere una singola espressione, facoltativamente racchiusa tra parentesi graffe.

Inizializza l'oggetto a zero (0 per numeri interi, 0.0per virgola mobile, un puntatore null per puntatori).

Per tipi non scalari (strutture, matrici, unioni), {0} specifica che il primo elemento dell'oggetto è inizializzato su zero. Per strutture contenenti strutture, matrici di strutture e così via, questo viene applicato in modo ricorsivo, quindi il primo elemento scalare viene impostato sullo zero, come appropriato per il tipo. Come in qualsiasi inizializzatore, tutti gli elementi non specificati sono impostati su zero.

Bretelle intermedie ( {,} ) possono essere omesse; ad esempio entrambi sono validi ed equivalenti:

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

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

motivo per cui non è necessario scrivere, ad esempio, { { 0 } } per un tipo il cui primo elemento non è scalare.

Così questo:

some_type obj = { 0 };

è un modo abbreviato di inizializzare obja zero, il che significa che ciascun oggetto secondario scalare objè impostato su 0se è un numero intero, 0.0se è in virgola mobile o un puntatore nullo se è un puntatore.

Le regole sono simili per C ++.

Nel tuo caso particolare, dato che stai assegnando valori a sexi.cbSizee così via, è chiaro che si SHELLEXECUTEINFOtratta di un tipo di struttura o di classe (o forse un sindacato, ma probabilmente no), quindi non tutto ciò si applica, ma come ho detto { 0 }è un comune linguaggio che può essere usato in situazioni più generali.

Questo non è (necessariamente) equivalente all'utilizzo memsetper impostare la rappresentazione dell'oggetto su all-bit-zero. Né il virgola mobile 0.0né un puntatore nullo sono necessariamente rappresentati come all-bit-zero e un { 0 }inizializzatore non imposta necessariamente il riempimento dei byte su alcun valore particolare. Sulla maggior parte dei sistemi, tuttavia, è probabile che abbia lo stesso effetto.


1
In C ++, {0}non è un inizializzatore valido per un oggetto che non accetta alcun costruttore 0; né per un aggregato il cui primo elemento è come tale (o un aggregato senza elementi)
MM

3

È passato un po 'di tempo da quando ho lavorato in c / c ++ ma IIRC, lo stesso collegamento può essere usato anche per gli array.


2

Mi sono sempre chiesto, perché dovresti usare qualcosa del genere

struct foo bar = { 0 };

Ecco un caso di prova per spiegare:

check.c

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

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

Compilo con gcc -O2 -o check check.ce quindi output la tabella dei simboli con readelf -s check | sort -k 2(questo è con gcc 4.6.3 su Ubuntu 12.04.2 su un sistema x64). Estratto:

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

La parte importante qui è, cioè my_zero_structdopo __bss_start. La sezione ".bss" in un programma C è la sezione di memoria che è impostata su zero prima che main venga chiamata vedi Wikipedia su .bss .

Se si modifica il codice sopra in:

} my_zero_struct = { 0 };

Quindi l'eseguibile "check" risultante sembra esattamente lo stesso almeno con il compilatore gcc 4.6.3 su Ubuntu 12.04.2; la my_zero_structè ancora in .bsssezione e quindi viene automaticamente inizializzato a zero, primamain chiamata.

Suggerimenti nei commenti, secondo cui una memsetpotrebbe inizializzare la struttura "completa" non è un miglioramento, poiché la .bsssezione viene cancellata completamente, il che significa anche che la struttura "piena" è impostata su zero.

È possibile che lo standard del linguaggio C non menzioni nulla di tutto ciò, ma in un compilatore C del mondo reale non ho mai visto un comportamento diverso.


Le variabili globali e statiche vengono sempre inizializzate per impostazione predefinita su 0 o su ctor predefinito. Ma se dichiari l'istanza di f localmente potresti ottenere risultati diversi.
Logman,

0

È zucchero sintetico inizializzare l'intera struttura su valori vuoti / zero / null.

Versione lunga

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;

Versione breve

SHELLEXECUTEINFO sexi = {0};

Non è stato molto più facile?

È anche bello perché:

  • non devi dare la caccia a tutti i membri e inizializzarlo
  • non devi preoccuparti di non poter inizializzare i nuovi membri quando vengono aggiunti successivamente
  • non devi chiamareZeroMemory

-5

{0} è un array anonimo che contiene il suo elemento come 0.

Questo è abituato inizializzare uno o tutti gli elementi dell'array con 0.

ad esempio int arr [8] = {0};

In questo caso tutti gli elementi di arr verranno inizializzati come 0.


4
{0}non è un array anonimo. Non è nemmeno un'espressione. È un inizializzatore.
Keith Thompson,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.