Co oznacza {0} podczas inicjowania obiektu?

252

{0}Co to znaczy, kiedy jest używany do inicjalizacji obiektu? {0}Nigdzie nie mogę znaleźć żadnych odniesień , a ze względu na nawiasy klamrowe wyszukiwania w Google nie są pomocne.

Przykładowy kod:

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

Bez tego powyższy kod ulegnie awarii podczas działania.

Mahmoud Al-Qudsi
źródło

Odpowiedzi:

302

To, co się tutaj dzieje, nazywa się inicjalizacją zbiorczą . Oto (skrócona) definicja agregatu z sekcji 8.5.1 specyfikacji ISO:

Agregacja to tablica lub klasa bez konstruktorów deklarowanych przez użytkownika, bez prywatnych lub chronionych niestatycznych elementów danych, bez klas podstawowych i bez funkcji wirtualnych.

Teraz użycie {0}do zainicjowania takiego agregatu jest w zasadzie sztuczką dla 0całej sprawy. Wynika to z faktu, że podczas korzystania z inicjalizacji zbiorczej nie trzeba określać wszystkich elementów, a specyfikacja wymaga domyślnej inicjalizacji wszystkich nieokreślonych elementów, co oznacza ustawienie 0na typy proste.

Oto odpowiedni cytat ze specyfikacji:

Jeśli na liście znajduje się mniej inicjatorów niż członków w agregacie, wówczas każdy członek, który nie został jawnie zainicjowany, zostanie zainicjowany domyślnie. Przykład:

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

inicjuje ss.asię 1, ss.bz "asdf"i ss.co wartości wyrażenia z formy int(), to znaczy 0.

Pełną specyfikację na ten temat można znaleźć tutaj

Don Neufeld
źródło
15
Doskonała odpowiedź. Chciałem tylko dodać, że inicjowanie agregatu za pomocą {0} jest tym samym, co inicjowanie za pomocą po prostu {}. Być może to pierwsze sprawia, że ​​bardziej oczywiste jest, że typy wbudowane są zerowane.
James Hopkin
7
Niektóre kompilatory dławią się na {}, dlatego przyzwyczaja się {0}
Branan
10
Niezupełnie .. W C ++, jeśli pierwszy element członkowski nie może zostać skonstruowany na zero, {0} nie będzie działać. Np .: struct A {B b; int i; char c; }; struct B {B (); B (ciąg); }; A a = {}; // tej instrukcji nie można przepisać jako „A a = {0}”.
Aaron
25
@Branan, to dlatego, że w C „{}” nie jest poprawny. W C ++ tak jest. @ don.neufeld, zmieniło się to w C ++ 03 (default-initialized -> value-initialized). Cytat przytacza standard C ++ 98, należy pamiętać.
Johannes Schaub - litb
3
Czy to zastępuje potrzebę korzystania z ZeroMemory () (w VC ++)?
Ray
89

Należy pamiętać, że ta technika nie ustawi bajtów wypełniania na zero. Na przykład:

struct foo
{
    char c;
    int  i;
};

foo a = {0};

To nie to samo co:

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

W pierwszym przypadku bajty padu między c i i nie są inicjowane. Dlaczego miałbyś się przejmować? Cóż, jeśli zapisujesz te dane na dysku lub wysyłasz je przez sieć lub cokolwiek innego, możesz mieć problem z bezpieczeństwem.

Harold Ekstrom
źródło
13
Oczywiście jest to tylko kwestia bezpieczeństwa, jeśli „piszesz (f, i a, sizeof (a))”, co może powodować różne kodowanie plików na różnych procesorach / kompilatorach. Dobrze sformatowane wyjście byłoby bezpieczne bez zestawu.
Aaron
3
Ponadto, jeśli wysyłasz rzeczy przez sieć, zawsze ustawiasz wyrównanie do spakowania. W ten sposób otrzymujesz jak najmniej dodatkowych bajtów wypełniania.
Mark Kegel,
18
Należy zauważyć, że chociaż specyfikacja nie wymaga zainicjowania paddingu, każdy rozsądny kompilator będzie go kosztował, ponieważ zainicjowanie go „wokół” kosztuje tylko czas.
Tomas
4
Chciałbym zakwestionować użycie słów „nie”. Bajty dopełniające są zdefiniowane w implementacji. Kompilator może dowolnie przekształcić foo a = {0} w zestaw (i a, 0, sizeof (a)). Nie jest wymagane „pomijanie” bajtów wypełniania i ustawianie tylko foo.c i foo.i. +1 za (potencjalny) błąd bezpieczeństwa
SecurityMatt
3
@Leushenko punkt 19 mówi „wszystkie podobiekty, które nie zostały jawnie zainicjowane”, więc przechyliłbym się do tego, że punkt 21 (który zacytowałeś) jest niechlujny. Byłoby naprawdę dziwne, gdyby specyfikacja pozwalała na niezainicjowanie dopełniania aż do momentu ostatniego inicjalizatora, po którym dopełnianie musiało być zerowane, szczególnie biorąc pod uwagę, że inicjatory mogą wydawać się nieczynne, gdy używane są wyznaczone inicjalizatory.
MM
20

Zauważ, że działa także pusty inicjator agregacji:

SHELLEXECUTEINFO sexi = {};
char mytext[100] = {};
dalle
źródło
11

W odpowiedzi na ShellExecuteEx()to, dlaczego ulega awarii: Twoja SHELLEXECUTEINFOstruktura „sexi” ma wielu członków, a ty tylko inicjujesz niektóre z nich.

Na przykład członek sexi.lpDirectorymoże wskazywać w dowolnym miejscu, ale ShellExecuteEx()nadal będzie próbował go użyć, dlatego wystąpi naruszenie zasad dostępu do pamięci.

Po dodaniu wiersza:

SHELLEXECUTEINFO sexi = {0};

przed resztą konfiguracji struktury mówisz kompilatorowi, aby wyzerował wszystkie elementy struktury, zanim zainicjujesz te, które Cię interesują. ShellExecuteEx()wie, że jeśli sexi.lpDirectorywynosi zero, powinno to zignorować.

snowcrash09
źródło
7

Używam go również do inicjowania ciągów znaków, np.

char mytext[100] = {0};
Adam Pierce
źródło
5
Chociaż oczywiście, jeśli używany jest ciąg tekstowy, ciąg znaków, char mytext [100]; mytext [0] = '\ 0'; miałby taki sam efekt, jak podanie pustego ciągu, ale tylko spowodowałby, że implementacja wyzerowała pierwszy bajt.
Chris Young,
@Chris: Często żałowałem, że nie było składni częściowej inicjalizacji obiektu. Możliwość „zadeklarowania i zainicjowania” x, a następnie zrobienia podobnie z y, następnie z, jest o wiele ładniejsza niż konieczność zadeklarowania x, y i z, a następnie zainicjowanie x, y i z, ale inicjowanie 100 bajtów, gdy tylko rzeczywiście potrzebna inicjalizacja wydaje się raczej marnotrawstwem.
supercat
7

{0}jest prawidłowym inicjatorem dla dowolnego typu (pełnego obiektu), zarówno w C, jak i C ++. Jest to powszechny idiom używany do inicjalizacji obiektu do zera (czytaj dalej, aby zobaczyć, co to oznacza).

W przypadku typów skalarnych (typy arytmetyczne i wskaźniki) nawiasy klamrowe są niepotrzebne, ale są wyraźnie dozwolone. Cytując projekt normy N1570 normy ISO C, sekcja 6.7.9:

Inicjalizatorem skalara powinno być pojedyncze wyrażenie, opcjonalnie zamknięte w nawiasach klamrowych.

Inicjuje obiekt do zera ( 0dla liczb całkowitych, 0.0dla liczb zmiennoprzecinkowych, zerowy wskaźnik dla wskaźników).

W przypadku typów innych niż skalarne (struktury, tablice, związki) {0}określa, że pierwszy element obiektu jest inicjowany na zero. W przypadku struktur zawierających struktury, tablice struktur itp. Stosuje się to rekurencyjnie, więc pierwszy element skalarny jest ustawiany na zero, odpowiednio do typu. Jak w każdym inicjalizatorze, wszelkie nieokreślone elementy są ustawiane na zero.

Pośrednie nawiasy klamrowe ( {, }) można pominąć; na przykład oba są prawidłowe i równoważne:

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

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

dlatego nie musisz na przykład pisać { { 0 } }dla typu, którego pierwszym elementem jest nieskalarny.

Więc to:

some_type obj = { 0 };

jest skrótowym sposobem inicjalizacji objdo zera, co oznacza, że ​​każdy skalarny podobiekt objjest ustawiony na, 0jeśli jest liczbą całkowitą, 0.0jeśli jest liczbą zmiennoprzecinkową, lub zerowym wskaźnikiem, jeśli jest wskaźnikiem.

Zasady są podobne dla C ++.

W twoim konkretnym przypadku, ponieważ przypisujesz wartości do sexi.cbSizeitd., Jasne SHELLEXECUTEINFOjest, że jest to struktura lub klasa (lub może związek, ale prawdopodobnie nie), więc nie wszystko to dotyczy, ale jak powiedziałem, { 0 }jest to powszechne idiom, którego można użyć w bardziej ogólnych sytuacjach.

Nie jest to (koniecznie) równoważne z użyciem memsetdo ustawienia reprezentacji obiektu na zero-zero. Ani 0.0wskaźnik zmiennoprzecinkowy, ani wskaźnik zerowy nie są koniecznie reprezentowane jako zero-bitowe, a { 0 }inicjator niekoniecznie ustawia bajty wypełniające na jakąkolwiek konkretną wartość. Jednak w większości systemów może to mieć ten sam efekt.

Keith Thompson
źródło
1
W C ++ {0}nie jest prawidłowym inicjatorem obiektu bez akceptacji konstruktora 0; ani dla agregatu, którego pierwszy element jest jako taki (lub agregatu bez elementów)
MM
3

Minęło trochę czasu, odkąd pracowałem w c / c ++, ale IIRC, ten sam skrót można również zastosować do tablic.

µBio
źródło
2

Zawsze zastanawiałem się, dlaczego powinieneś użyć czegoś takiego

struct foo bar = { 0 };

Oto przypadek testowy do wyjaśnienia:

czek. c

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

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

Kompiluję z, gcc -O2 -o check check.ca następnie readelf -s check | sort -k 2wypisuję tabelę symboli za pomocą (to jest z gcc 4.6.3 na Ubuntu 12.04.2 na systemie x64). Fragment:

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

Ważną częścią jest to, że my_zero_structpóźniej __bss_start. Sekcja „.bss” w programie C to sekcja pamięci, która jest zerowana, zanim main zostanie nazwana patrz wikipedia na .bss .

Jeśli zmienisz powyższy kod na:

} my_zero_struct = { 0 };

Wynikowy plik wykonywalny „check” wygląda dokładnie tak samo, przynajmniej w kompilatorze gcc 4.6.3 na Ubuntu 12.04.2; my_zero_structjest nadal w .bsspunkcie a tym samym zostanie on automatycznie inicjowany do zera, zanim mainnazywa.

Wskazówki w komentarzach, że a memsetmoże zainicjować „pełną” strukturę, również nie stanowi ulepszenia, ponieważ .bsssekcja jest całkowicie wyczyszczona, co oznacza również, że „pełna” struktura jest ustawiona na zero.

Być może standard języka C nie wspomina o tym, ale w prawdziwym kompilatorze C nigdy nie widziałem innego zachowania.

Ingo Blackman
źródło
Zmienne globalne i statyczne są zawsze inicjowane domyślnie na 0 lub domyślny ctor. Ale jeśli zadeklarujesz instancję f lokalnie, możesz uzyskać różne wyniki.
Logman
0

To cukier syntetyczny, który inicjuje całą strukturę do wartości pustych / zerowych / zerowych.

Długa wersja

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;

Krótka wersja

SHELLEXECUTEINFO sexi = {0};

Czy to nie było o wiele łatwiejsze?

Jest to również miłe, ponieważ:

  • nie musisz tropić każdego członka i inicjować go
  • nie musisz się martwić, że możesz nie inicjować nowych członków, gdy zostaną dodani później
  • nie musisz dzwonićZeroMemory
Ian Boyd
źródło
-5

{0} jest anonimową tablicą zawierającą jej element jako 0.

Służy do inicjalizacji jednego lub wszystkich elementów tablicy z 0.

np. int arr [8] = {0};

W takim przypadku wszystkie elementy arr zostaną zainicjowane jako 0.

nitin prajapati
źródło
4
{0}nie jest anonimową tablicą. To nawet nie jest wyrażenie. To inicjator.
Keith Thompson,