O que {0} significa ao inicializar um objeto?

252

Quando {0}é usado para inicializar um objeto, o que isso significa? Não consigo encontrar referências a {0}nenhum lugar e, por causa das chaves, as pesquisas no Google não são úteis.

Código de exemplo:

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

Sem ele, o código acima falhará no tempo de execução.

Mahmoud Al-Qudsi
fonte

Respostas:

302

O que está acontecendo aqui é chamado de inicialização agregada . Aqui está a definição (abreviada) de um agregado da seção 8.5.1 da especificação ISO:

Um agregado é uma matriz ou uma classe sem construtores declarados pelo usuário, sem membros de dados não estáticos privados ou protegidos, sem classes de base e sem funções virtuais.

Agora, usar {0}para inicializar um agregado como esse é basicamente um truque para 0a coisa toda. Isso ocorre porque ao usar a inicialização agregada, você não precisa especificar todos os membros e a especificação exige que todos os membros não especificados sejam inicializados por padrão, o que significa definido 0para tipos simples.

Aqui está a citação relevante da especificação:

Se houver menos inicializadores na lista do que membros no agregado, cada membro não explicitamente inicializado será inicializado por padrão. Exemplo:

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

inicializa ss.acom 1, ss.bcom "asdf"e ss.ccom o valor de uma expressão do formulário int(), ou seja 0,.

Você pode encontrar a especificação completa deste tópico aqui

Don Neufeld
fonte
15
Excelente resposta. Só queria acrescentar que inicializar um agregado com {0} é o mesmo que inicializá-lo com simplesmente {}. Talvez o primeiro torne mais óbvio que os tipos internos são zerados.
James Hopkin
7
Alguns compiladores engasgar com {}, é por isso {0} se acostuma
Branan
10
Não é bem assim. No C ++, se o primeiro membro não puder ser construído com zero, {0} não funcionará. Ex: struct A {B b; int i; char c; }; struct B {B (); B (string); }; A a = {}; // esta declaração não pode ser reescrita como 'A a = {0}'.
Aaron
25
@Branan, é porque em C "{}" não é válido. Em C ++ é. @ don.neufeld, isso mudou com o C ++ 03 (inicializado por padrão -> inicializado por valor). A citação cita o padrão C ++ 98, lembre-se.
Johannes Schaub - litb
3
Então, isso substitui a necessidade de usar ZeroMemory () (no VC ++)?
22414 Ray
89

Uma coisa a ter em atenção é que essa técnica não definirá bytes de preenchimento como zero. Por exemplo:

struct foo
{
    char c;
    int  i;
};

foo a = {0};

Não é o mesmo que:

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

No primeiro caso, os bytes do pad entre c e i não são inicializados. Por que você se importaria? Bem, se você estiver salvando esses dados em disco ou enviando-os por uma rede ou qualquer outra coisa, poderá ter um problema de segurança.

Harold Ekstrom
fonte
13
Obviamente, é apenas um problema de segurança se você escrever (f, & a, sizeof (a)) ', que pode produzir codificação de arquivo diferente em diferentes processadores / compiladores. Uma saída bem formatada seria segura sem o memset.
Aaron
3
Além disso, se você estiver enviando coisas pela rede, sempre definirá o alinhamento a ser compactado. Dessa forma, você obtém o mínimo de bytes de preenchimento extra possível.
Mark Kegel
18
Deve-se notar que, embora a especificação não exija que o preenchimento seja inicializado, qualquer compilador sadio exigirá, pois só custa tempo para inicializá-lo.
Tomas
4
Eu gostaria de ter problemas com o uso das palavras "não é". Bytes de preenchimento são definidos de implementação. O compilador é livre para transformar foo a = {0} em memset (& a, 0, sizeof (a)) como quiser. Não é necessário "pular" bytes de preenchimento e definir apenas foo.c e foo.i. +1 para o (potencial) bug de segurança tho #
SecurityMatt
3
@ O ponto 19 de Leushenko diz "todos os subobjetos que não foram inicializados explicitamente", então eu me inclinaria para o ponto 21 (que você citou) sendo desleixado. Seria realmente bizarro se a especificação permitisse que o preenchimento fosse inicializado até o momento do último inicializador, após o qual o preenchimento precisou ser zerado, especialmente considerando que os inicializadores podem parecer fora de ordem quando os inicializadores designados são usados.
MM
20

Observe que um inicializador agregado vazio também funciona:

SHELLEXECUTEINFO sexi = {};
char mytext[100] = {};
Dalle
fonte
11

Em resposta ao porquê ShellExecuteEx()está falhando: sua SHELLEXECUTEINFOestrutura "sexi" tem muitos membros e você está inicializando apenas alguns deles.

Por exemplo, o membro sexi.lpDirectorypode estar apontando para qualquer lugar, mas ShellExecuteEx()ainda tentará usá-lo; portanto, você terá uma violação de acesso à memória.

Quando você inclui a linha:

SHELLEXECUTEINFO sexi = {0};

antes do resto da configuração da sua estrutura, você está dizendo ao compilador para zerar todos os membros da estrutura antes de inicializar os membros específicos em que você está interessado. ShellExecuteEx()sabe que se sexi.lpDirectoryfor zero, deve ignorá-lo.

snowcrash09
fonte
7

Eu também o uso para inicializar strings, por exemplo.

char mytext[100] = {0};
Adam Pierce
fonte
5
Embora, é claro, se mytext estiver sendo usado como uma string, char mytext [100]; meu texto [0] = '\ 0'; teria o mesmo efeito de fornecer uma cadeia vazia, mas apenas causaria a implementação em zero o primeiro byte.
317 Chris Young
@ Chris: Eu sempre desejei que houvesse uma sintaxe para a inicialização parcial do objeto. Ser capaz de "declarar e inicializar" x, e fazer o mesmo com y e z é muito melhor do que declarar x, ye z e inicializar x, y e z, mas inicializando 100 bytes quando apenas alguém realmente precisa de inicialização parece um desperdício.
supercat 12/12
7

{0}é um inicializador válido para qualquer tipo (objeto completo), em C e C ++. É um idioma comum usado para inicializar um objeto para zero (leia para ver o que isso significa).

Para tipos escalares (tipos aritmético e ponteiro), as chaves são desnecessárias, mas são explicitamente permitidas. Citando o rascunho N1570 da norma ISO C, seção 6.7.9:

O inicializador para um escalar deve ser uma expressão única, opcionalmente entre chaves.

Inicializa o objeto para zero ( 0para números inteiros, 0.0para ponto flutuante, um ponteiro nulo para ponteiros).

Para tipos não escalares (estruturas, matrizes, uniões), {0}especifica que o primeiro elemento do objeto é inicializado como zero. Para estruturas que contêm estruturas, matrizes de estruturas e assim por diante, isso é aplicado recursivamente, para que o primeiro elemento escalar seja definido como zero, conforme apropriado para o tipo. Como em qualquer inicializador, qualquer elemento não especificado é definido como zero.

Chaves intermediárias ( {, }) podem ser omitidas; por exemplo, ambos são válidos e equivalentes:

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

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

é por isso que você não precisa escrever, por exemplo, { { 0 } }para um tipo cujo primeiro elemento não seja escalar.

Então, é isso:

some_type obj = { 0 };

é uma maneira abreviada de inicializar objpara zero, o que significa que cada subobjeto escalar de objé definido como 0se for um número inteiro, 0.0se for um ponto flutuante ou um ponteiro nulo, se for um ponteiro.

As regras são semelhantes para C ++.

No seu caso particular, como você está atribuindo valores para sexi.cbSizee assim por diante, fica claro que SHELLEXECUTEINFOé um tipo de estrutura ou classe (ou possivelmente uma união, mas provavelmente não), então nem tudo isso se aplica, mas, como eu disse, { 0 }é comum idioma que pode ser usado em situações mais gerais.

Isso não é (necessariamente) equivalente a usar memsetpara definir a representação do objeto como all-bits-zero. Nem o ponto flutuante 0.0nem o ponteiro nulo são necessariamente representados como zero de todos os bits, e um { 0 }inicializador não define necessariamente bytes de preenchimento para nenhum valor específico. Na maioria dos sistemas, porém, é provável que tenha o mesmo efeito.

Keith Thompson
fonte
1
No C ++, {0}não é um inicializador válido para um objeto sem aceitação do construtor 0; nem para um agregado cujo primeiro elemento é como tal (ou um agregado sem elementos)
MM
3

Já faz um tempo desde que eu trabalhei em c / c ++, mas no IIRC, o mesmo atalho também pode ser usado para matrizes.

µBio
fonte
2

Eu sempre me perguntei por que você deveria usar algo como

struct foo bar = { 0 };

Aqui está um caso de teste para explicar:

check.c

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

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

Eu compilo com gcc -O2 -o check check.ce depois readelf -s check | sort -k 2saio da tabela de símbolos (isto é com o gcc 4.6.3 no ubuntu 12.04.2 em um sistema x64). Excerto:

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

A parte importante aqui é que my_zero_structé depois __bss_start. A seção ".bss" em um programa C é a seção da memória que é definida como zero antes de main ser chamada, consulte a wikipedia em .bss .

Se você alterar o código acima para:

} my_zero_struct = { 0 };

Em seguida, o executável "check" resultante parece exatamente o mesmo pelo menos com o compilador gcc 4.6.3 no ubuntu 12.04.2; o my_zero_structainda está na .bssseção e, portanto, será automaticamente inicializado como zero, antes de mainser chamado.

Dicas nos comentários, de que um memsetpode inicializar a estrutura "completa" também não é uma melhoria, porque a .bssseção é totalmente limpa, o que também significa que a estrutura "completa" está definida como zero.

Ele pode ser que o padrão de linguagem C não menciona nada disso, mas em um compilador mundo real C Eu nunca vi um comportamento diferente.

Ingo Blackman
fonte
Variáveis ​​globais e estáticas são sempre inicializadas por padrão como 0 ou controlador padrão. Mas se você declarar instância de f localmente, poderá obter resultados diferentes.
Logman
0

É açúcar sintático inicializar toda a sua estrutura para valores vazios / zero / nulo.

Versão longa

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;

Versão curta

SHELLEXECUTEINFO sexi = {0};

Não foi muito mais fácil?

Também é legal porque:

  • você não precisa caçar todos os membros e inicializá-lo
  • você não precisa se preocupar em não inicializar novos membros quando eles forem adicionados posteriormente
  • você não precisa ligarZeroMemory
Ian Boyd
fonte
-5

{0} é uma matriz anônima que contém seu elemento como 0.

Isso é usado para inicializar um ou todos os elementos da matriz com 0.

por exemplo, int arr [8] = {0};

Nesse caso, todos os elementos de arr serão inicializados como 0.

nitin prajapati
fonte
4
{0}não é uma matriz anônima. Nem sequer é uma expressão. É um inicializador.
Keith Thompson