¿Qué significa {0} al inicializar un objeto?


252

Cuando {0}se usa para inicializar un objeto, ¿qué significa? No puedo encontrar ninguna referencia a {0}ninguna parte y, debido a las llaves, las búsquedas de Google no son útiles.

Código de ejemplo:

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

Sin él, el código anterior se bloqueará en tiempo de ejecución.

Respuestas:


302

Lo que sucede aquí se llama inicialización agregada . Aquí está la definición (abreviada) de un agregado de la sección 8.5.1 de la especificación ISO:

Un agregado es una matriz o una clase sin constructores declarados por el usuario, sin miembros de datos no estáticos privados o protegidos, sin clases base y sin funciones virtuales.

Ahora, usar {0}para inicializar un agregado como este es básicamente un truco para 0todo. Esto se debe a que cuando se usa la inicialización agregada no es necesario que especifique todos los miembros y la especificación requiere que todos los miembros no especificados se inicialicen de manera predeterminada, lo que significa que se debe establecer en 0tipos simples.

Aquí está la cita relevante de la especificación:

Si hay menos inicializadores en la lista que miembros en el agregado, entonces cada miembro no inicializado explícitamente se inicializará por defecto. Ejemplo:

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

inicializa ss.acon 1, ss.bcon "asdf", y ss.ccon el valor de una expresión de la forma int(), es decir, 0.

Puede encontrar la especificación completa sobre este tema aquí


15
Excelente respuesta Solo quería agregar que inicializar un agregado con {0} es lo mismo que inicializarlo con simplemente {}. Quizás el primero hace que sea más obvio que los tipos integrados se ponen a cero.
James Hopkin

77
Algunos compiladores se ahogan con {}, es por eso que {0} se usa
Branan

10
No del todo. En C ++, si el primer miembro no puede construirse con cero, entonces {0} no funcionará. Ej: estructura A {B b; int i; char c; }; estructura B {B (); B (cadena); }; A a = {}; // esta declaración no puede reescribirse como 'A a = {0}'.
Aaron

25
@Branan, eso es porque en C "{}" no es válido. En C ++ lo es. @ don.neufeld, esto ha cambiado con C ++ 03 (inicializado por defecto -> inicializado por valor). La cita cita el estándar C ++ 98, tenga en cuenta.
Johannes Schaub - litb

3
Entonces, ¿esto reemplaza la necesidad de usar ZeroMemory () (en VC ++)?
Ray

89

Una cosa a tener en cuenta es que esta técnica no establecerá bytes de relleno en cero. Por ejemplo:

struct foo
{
    char c;
    int  i;
};

foo a = {0};

No es lo mismo que:

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

En el primer caso, los bytes de relleno entre cy i no se inicializan. ¿Por qué te importaría? Bueno, si está guardando estos datos en el disco o enviándolos a través de una red o lo que sea, podría tener un problema de seguridad.


13
Por supuesto, solo es un problema de seguridad si 'escribe (f, & a, sizeof (a))', que puede producir una codificación de archivo diferente en diferentes procesadores / compiladores. Una salida bien formateada sería segura sin el conjunto de memoria.
Aaron

3
Además, si está enviando cosas a través de la red, siempre configurará la alineación para que esté empaquetada. De esa forma, obtienes la menor cantidad de bytes de relleno adicionales posible.
Mark Kegel

18
Cabe señalar que, aunque la especificación no requiere que se inicialice el relleno, cualquier compilador sensato lo hará, ya que solo cuesta tiempo inicializarlo.
Tomás

44
Me gustaría estar en desacuerdo con el uso de las palabras "no es". Los bytes de relleno están definidos de implementación. El compilador es libre de convertir foo a = {0} en memset (& a, 0, sizeof (a)) a su propio ritmo. No es necesario "omitir" los bytes de relleno y solo establecer foo.c y foo.i. +1 para el (potencial) error de seguridad aunque
SecurityMatt

3
@Leushenko el punto 19 dice "todos los subobjetos que no se inicializan explícitamente", por lo que me inclino por el punto 21 (que usted citó) que es descuidado. Sería realmente extraño si la especificación permitiera que el relleno no se inicializara hasta el momento del último inicializador, después de lo cual el relleno tuvo que ponerse a cero, especialmente teniendo en cuenta que los inicializadores pueden aparecer fuera de servicio cuando se utilizan los inicializadores designados.
MM

20

Tenga en cuenta que un inicializador agregado vacío también funciona:

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

11

En respuesta a por qué se ShellExecuteEx()está bloqueando: su SHELLEXECUTEINFOestructura "sexi" tiene muchos miembros y solo está inicializando algunos de ellos.

Por ejemplo, el miembro sexi.lpDirectorypodría estar apuntando a cualquier parte, pero ShellExecuteEx()aún intentará usarlo, por lo tanto, obtendrá una infracción de acceso a la memoria.

Cuando incluye la línea:

SHELLEXECUTEINFO sexi = {0};

antes del resto de la configuración de su estructura, le está diciendo al compilador que ponga a cero todos los miembros de la estructura antes de inicializar los específicos que le interesan. ShellExecuteEx()Sabe que si sexi.lpDirectoryes cero, debería ignorarlo.


7

También lo uso para inicializar cadenas, por ejemplo.

char mytext[100] = {0};

55
Aunque, por supuesto, si mytext se está utilizando como una cadena, char mytext [100]; mytext [0] = '\ 0'; tendría el mismo efecto de dar una cadena vacía, pero solo provocaría que la implementación ponga a cero el primer byte.
Chris Young

@ Chris: Muchas veces he deseado que hubiera una sintaxis para la inicialización parcial de objetos. Ser capaz de "declarar e inicializar" x, luego hacer lo mismo con y, luego z, es mucho mejor que tener que declarar x, y y z, y luego inicializar x, y y z, pero inicializando 100 bytes cuando solo uno realmente necesita inicialización parece bastante derrochador.
supercat

7

{0}es un inicializador válido para cualquier tipo (objeto completo), tanto en C como en C ++. Es un idioma común usado para inicializar un objeto a cero ( sigue leyendo para ver qué significa eso).

Para los tipos escalares (tipos aritméticos y de puntero), los corchetes son innecesarios, pero están explícitamente permitidos. Citando el borrador N1570 del estándar ISO C, sección 6.7.9:

El inicializador para un escalar debe ser una sola expresión, opcionalmente encerrada entre llaves.

Inicializa el objeto a cero ( 0para enteros, 0.0para coma flotante, un puntero nulo para punteros).

Para los tipos no escalares (estructuras, matrices, uniones), {0}especifica que el primer elemento del objeto se inicializa a cero. Para estructuras que contienen estructuras, matrices de estructuras, etc., esto se aplica de forma recursiva, por lo que el primer elemento escalar se establece en cero, según corresponda para el tipo. Como en cualquier inicializador, cualquier elemento no especificado se establece en cero.

Se pueden omitir llaves intermedias ( {, }); Por ejemplo, ambos son válidos y equivalentes:

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

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

Es por eso que no tiene que escribir, por ejemplo, { { 0 } }para un tipo cuyo primer elemento no es escalar.

Así que esto:

some_type obj = { 0 };

es una forma abreviada de inicializar obja cero, lo que significa que cada subobjeto escalar de objse establece en 0si es un entero, 0.0si es un punto flotante o un puntero nulo si es un puntero.

Las reglas son similares para C ++.

En su caso particular, dado que está asignando valores sexi.cbSize, etc., está claro que SHELLEXECUTEINFOes una estructura o un tipo de clase (o posiblemente una unión, pero probablemente no), por lo que no todo esto se aplica, pero como dije { 0 }es algo común modismo que se puede usar en situaciones más generales.

Esto no es (necesariamente) equivalente a usar memsetpara establecer la representación del objeto en todos-bits-cero. Ni el punto flotante 0.0ni un puntero nulo se representan necesariamente como todos los bits cero, y un { 0 }inicializador no establece necesariamente los bytes de relleno en ningún valor en particular. Sin embargo, en la mayoría de los sistemas, es probable que tenga el mismo efecto.


1
En C ++, {0}no es un inicializador válido para un objeto sin que el constructor lo acepte 0; ni para un agregado cuyo primer elemento es como tal (o un agregado sin elementos)
MM

3

Ha pasado un tiempo desde que trabajé en c / c ++ pero IIRC, el mismo acceso directo también se puede usar para matrices.


2

Siempre me he preguntado por qué deberías usar algo como

struct foo bar = { 0 };

Aquí hay un caso de prueba para explicar:

check.c

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

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

Compilo gcc -O2 -o check check.cy luego envío la tabla de símbolos con readelf -s check | sort -k 2(esto es con gcc 4.6.3 en ubuntu 12.04.2 en un sistema x64). Extracto:

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 aquí es, eso my_zero_structes después __bss_start. La sección ".bss" en un programa en C es la sección de memoria que se establece en cero antes de main llamarse ver wikipedia en .bss .

Si cambia el código anterior a:

} my_zero_struct = { 0 };

Luego, el ejecutable "check" resultante se ve exactamente igual al menos con el compilador gcc 4.6.3 en ubuntu 12.04.2; la my_zero_structsigue en la .bsssección y por lo tanto, se inicializará automáticamente a cero, antes de mainque se llama.

Sugerencias en los comentarios, que un memsetpodría inicializar la estructura "completa" tampoco es una mejora, porque la .bsssección se borra completamente, lo que también significa que la estructura "completa" se establece en cero.

Que podría ser que el lenguaje estándar C no menciona nada de esto, pero en un verdadero compilador C mundo que nunca han visto un comportamiento diferente.


Las variables globales y estáticas siempre se inicializan por defecto a 0 o ctor por defecto. Pero si declara la instancia de f localmente, puede obtener resultados diferentes.
Logman

0

Es el azúcar sintáctico para inicializar toda su estructura a valores vacíos / cero / nulos.

Versión larga

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;

Version corta

SHELLEXECUTEINFO sexi = {0};

¿No fue mucho más fácil?

También es bueno porque:

  • no tienes que cazar a cada miembro e inicializarlo
  • no tiene que preocuparse de no poder inicializar nuevos miembros cuando se agreguen más tarde
  • no tienes que llamarZeroMemory

-5

{0} es una matriz anónima que contiene su elemento como 0.

Esto se usa para inicializar uno o todos los elementos de la matriz con 0.

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

En este caso, todos los elementos de arr se inicializarán como 0.


44
{0}No es una matriz anónima. Ni siquiera es una expresión. Es un inicializador.
Keith Thompson
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.