Что означает {0} при инициализации объекта?

252

Когда {0}это используется для инициализации объекта, что это значит? Я нигде не могу найти никаких ссылок {0}, и из-за фигурных скобок поиск в Google не помогает.

Пример кода:

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

Без этого приведенный выше код будет зависать во время выполнения.

Махмуд Аль-Кудси
источник

Ответы:

302

То, что здесь происходит, называется агрегатной инициализацией. Вот (сокращенное) определение агрегата из раздела 8.5.1 спецификации ISO:

Агрегат - это массив или класс без объявленных пользователем конструкторов, без закрытых или защищенных нестатических элементов данных, без базовых классов и виртуальных функций.

Теперь, использование {0}для инициализации агрегата, как это, по сути, хитрость для 0всего этого. Это связано с тем, что при использовании агрегатной инициализации вам не нужно указывать все элементы, а спецификация требует, чтобы все неопределенные элементы инициализировались по умолчанию, что означает, что 0для простых типов установлено значение.

Вот соответствующая цитата из спецификации:

Если в списке меньше инициализаторов, чем элементов в агрегате, то каждый элемент, не инициализированный явно, должен инициализироваться по умолчанию. Пример:

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

инициализируется ss.aс 1, ss.bс "asdf"и ss.cсо значением выражения формы int(), то есть 0.

Вы можете найти полную спецификацию на эту тему здесь

Дон Нойфельд
источник
15
Отличный ответ. Просто хотел добавить, что инициализация агрегата с {0} - это то же самое, что инициализация его просто {}. Возможно, первое делает более очевидным, что встроенные типы обнуляются.
Джеймс Хопкин
7
Некоторые компиляторы подавляются {}, поэтому {0} привыкает
Бранан
10
Не совсем .. В C ++, если первый член не может быть построен с нуля, {0} не будет работать. Пример: структура A {B b; int i; символ с; }; struct B {B (); В (строка); }; A a = {}; // это утверждение нельзя переписать как 'A a = {0}'.
Аарон
25
@Branan, это потому, что в C "{}" недопустимо. В С ++ это так. @ don.neufeld, это изменилось в C ++ 03 (default-initialized -> value-initialized). Примите во внимание цитату C ++ 98 Standard.
Йоханнес Шауб -
3
Итак, заменяет ли это необходимость использования ZeroMemory () (в VC ++)?
Ray
89

Следует помнить, что этот метод не устанавливает нулевые байты заполнения. Например:

struct foo
{
    char c;
    int  i;
};

foo a = {0};

Это не то же самое, что:

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

В первом случае байты pad между c и i неинициализированы. Почему тебя это волнует? Ну, если вы сохраняете эти данные на диск или отправляете их по сети или что-то еще, у вас может быть проблема безопасности.

Гарольд Экстрем
источник
13
Конечно, это проблема безопасности, только если вы пишете (f, & a, sizeof (a)), что может привести к разной кодировке файлов на разных процессорах / компиляторах. Хорошо отформатированный вывод будет безопасным без memset.
Аарон
3
Кроме того, если вы отправляете данные по сети, вы всегда будете устанавливать выравнивание для упаковки. Таким образом вы получите как можно меньше дополнительных байтов заполнения.
Марк Кегель
18
Следует отметить, что хотя спецификация не требует инициализации дополнения, это будет делать любой здравомыслящий компилятор, так как для его инициализации требуется только время.
Томас
4
Я хотел бы взять вопрос с использованием слов "нет". Заполняющие байты определяются реализацией. Компилятор может свободно превращать foo a = {0} в memset (& a, 0, sizeof (a)) по своему усмотрению. Не требуется «пропускать» байты заполнения и устанавливать только foo.c и foo.i. +1 за (потенциальную) ошибку безопасности
SecurityMatt
3
@ Льюшенко, пункт 19, говорит «все подобъекты, которые не инициализированы явно», поэтому я бы предпочел, чтобы пункт 21 (который вы цитировали) был небрежным. Было бы действительно странно, если бы спецификация позволяла неинициализировать заполнение вплоть до момента последнего инициализатора, после чего заполнение пришлось обнулять, особенно если учесть, что инициализаторы могут появляться не в порядке при использовании назначенных инициализаторов.
ММ
20

Обратите внимание, что пустой инициализатор агрегата также работает:

SHELLEXECUTEINFO sexi = {};
char mytext[100] = {};
напольная плитка
источник
11

В ответ на вопрос, почему ShellExecuteEx()происходит сбой: в вашей структуре SHELLEXECUTEINFO"sexi" много членов, и вы только инициализируете некоторые из них.

Например, член sexi.lpDirectoryможет указывать куда угодно, но ShellExecuteEx()все равно будет пытаться его использовать, поэтому вы получите нарушение доступа к памяти.

Когда вы включаете строку:

SHELLEXECUTEINFO sexi = {0};

перед остальными настройками структуры вы говорите компилятору обнулить все элементы структуры, прежде чем инициализировать те, которые вас интересуют. Он ShellExecuteEx()знает, что если он sexi.lpDirectoryравен нулю, он должен его игнорировать.

snowcrash09
источник
7

Я также использую его для инициализации строк, например.

char mytext[100] = {0};
Адам Пирс
источник
5
Хотя, конечно, если mytext используется строка, char mytext [100]; mytext [0] = '\ 0'; будет иметь тот же эффект, что и пустая строка, но только приведет к тому, что реализация обнулит первый байт.
Крис Янг
@Chris: мне часто хотелось, чтобы был синтаксис для частичной инициализации объекта. Возможность «объявить и инициализировать» x, а затем сделать то же самое с y, затем с z, намного приятнее, чем необходимость объявлять x, y и z, а затем инициализировать x, y и z, но инициализировать 100 байтов, когда только на самом деле нужна инициализация, кажется довольно расточительной.
Суперкат
7

{0}является допустимым инициализатором для любого (завершенного объекта) типа, как в C, так и в C ++. Это общая идиома, используемая для инициализации объекта в ноль (читайте дальше, чтобы увидеть, что это значит).

Для скалярных типов (арифметических и указательных типов) фигурные скобки не нужны, но они явно разрешены. Цитируя проект N1570 стандарта ISO C, раздел 6.7.9:

Инициализатор для скаляра должен быть одним выражением, необязательно заключенным в фигурные скобки.

Он инициализирует объект в ноль ( 0для целых чисел, 0.0для чисел с плавающей точкой, нулевой указатель для указателей).

Для нескалярных типов (структуры, массивы, объединения) {0}указывает, что первый элемент объекта инициализируется нулем. Для структур, содержащих структуры, массивы структур и т. Д., Это применяется рекурсивно, поэтому первый скалярный элемент устанавливается в ноль, в зависимости от типа. Как и в любом инициализаторе, любые не указанные элементы устанавливаются в ноль.

Промежуточные скобки ( {, }) могут быть опущены; например, оба они действительны и эквивалентны:

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

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

вот почему вам не нужно писать, например, { { 0 } }для типа, чей первый элемент не является скалярным.

Итак, это:

some_type obj = { 0 };

это сокращенный способ инициализации objв ноль, означающий, что каждый скалярный подобъект objимеет значение, 0если оно является целым числом, 0.0если оно с плавающей запятой, или нулевым указателем, если это указатель.

Правила похожи на C ++.

В вашем конкретном случае, поскольку вы присваиваете значения sexi.cbSizeи т. Д., Ясно, что SHELLEXECUTEINFOэто структура или тип класса (или, возможно, объединение, но, вероятно, нет), поэтому не все это применимо, но, как я уже сказал { 0 }, это распространенное явление. идиома, которая может быть использована в более общих ситуациях.

Это не (обязательно) эквивалентно использованию memsetдля установки представления объекта равным нулю. Ни 0.0указатель с плавающей точкой, ни нулевой указатель не обязательно представляются как ноль со всеми битами, и { 0 }инициализатор не обязательно устанавливает байты заполнения в какое-либо конкретное значение. Однако на большинстве систем это может иметь такой же эффект.

Кит Томпсон
источник
1
В C ++ {0}не является допустимым инициализатором для объекта без принятия конструктора 0; ни для агрегата, чей первый элемент как таковой (или агрегат без элементов)
MM
3

Прошло некоторое время с тех пор, как я работал на c / c ++, но в IIRC такой же ярлык можно использовать и для массивов.

μBio
источник
2

Я всегда задавался вопросом, почему вы должны использовать что-то вроде

struct foo bar = { 0 };

Вот тестовый пример, чтобы объяснить:

check.c

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

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

Я компилирую с gcc -O2 -o check check.cи затем readelf -s check | sort -k 2выводю таблицу символов с помощью (это с gcc 4.6.3 на Ubuntu 12.04.2 в системе x64). Выдержка:

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

Важной частью здесь является то, что my_zero_structпосле __bss_start. Раздел «.bss» в программе на C - это раздел памяти, который устанавливается в ноль, прежде чем main вызывается, см. Википедию на .bss .

Если вы измените код выше на:

} my_zero_struct = { 0 };

Тогда полученный исполняемый файл «check» выглядит точно так же, по крайней мере, с компилятором gcc 4.6.3 в ubuntu 12.04.2; my_zero_structвсе еще находится в .bssразделе и , таким образом , он будет автоматически инициализируются нулем, перед тем mainназывается.

Подсказки в комментариях, что memsetможет инициализировать «полную» структуру, также не является улучшением, потому что .bssраздел очищен полностью, что также означает, что «полная» структура установлена ​​в ноль.

Это может быть то , что стандарт языка C не говоря уже о каком - либо из этого, но в реальном компиляторе мир C Я никогда не видел другое поведение.

Инго Блэкман
источник
Глобальные и статические переменные всегда инициализируются по умолчанию 0 или ctor по умолчанию. Но если вы объявите экземпляр f локально, вы можете получить разные результаты.
Логман
0

Это синтетический сахар для инициализации всей вашей структуры пустыми / нулевыми / нулевыми значениями.

Длинная версия

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;

Укороченная версия

SHELLEXECUTEINFO sexi = {0};

Разве это не было намного проще?

Это также приятно, потому что:

  • вам не нужно выслеживать каждого участника и инициализировать его
  • вам не нужно беспокоиться, что вы не сможете инициализировать новых участников, когда они будут добавлены позже
  • вам не нужно звонитьZeroMemory
Ян Бойд
источник
-5

{0} - это анонимный массив, содержащий его элемент как 0.

Это используется для инициализации одного или всех элементов массива с 0.

например, int arr [8] = {0};

В этом случае все элементы arr будут инициализированы как 0.

нитин праджапати
источник
4
{0}не является анонимным массивом Это даже не выражение. Это инициализатор.
Кит Томпсон