2009-11-20 18 views
5

En todas partes donde miro hay personas que argumentan vociferentemente que las variables no inicializadas son malas y ciertamente estoy de acuerdo y entiendo por qué, sin embargo; mi pregunta es, ¿hay ocasiones en que no quieras hacer esto?Inicializando matrices en C++

Por ejemplo, tomemos el código:

char arrBuffer[1024] = { '\0' }; 

¿El anulación toda la gama crear un impacto en el rendimiento sobre el uso de la matriz sin inicializar ella?

+1

Su ejemplo no anula toda la matriz, solo el primer elemento ... –

+13

En realidad, * hace * anula toda la matriz. – paxdiablo

+5

El ejemplo inicializa el primer elemento de la matriz con un '\ 0' específico y todos los demás con un '\ 0' predeterminado. El código 'char a [4] = {'A'};' pone 'A' en un [0], '\ 0' en un [1] ... – pmg

Respuesta

10

que asumen una pila de inicialización debido a arreglos estáticos son auto-inicializado.
G ++ salida

char whatever[2567] = {'\0'}; 
    8048530:  8d 95 f5 f5 ff ff  lea -0xa0b(%ebp),%edx 
    8048536:  b8 07 0a 00 00   mov $0xa07,%eax 
    804853b:  89 44 24 08    mov %eax,0x8(%esp) 
    804853f:  c7 44 24 04 00 00 00 movl $0x0,0x4(%esp) 
    8048546:  00 
    8048547:  89 14 24    mov %edx,(%esp) 
    804854a:  e8 b9 fe ff ff   call 8048408 <[email protected]> 

Así, se inicializa con { '\ 0'} y se realiza una llamada a memset, así que sí, que tienen un impacto en el rendimiento.

+0

(+1), pero ¿y si la matriz no es estática? – Konrad

+0

El ejemplo es precisamente para matrices no estáticas, matrices basadas en pila. Si con no estático se quiere decir tamaño desconocido (como en C99) en tiempo de compilación, el código sería más o menos el mismo ya que memset siempre se llama para anularlo. –

-2

Y ¿por qué le importan los beneficios de rendimiento, cuánto rendimiento obtendrá al no inicializarlo, y es más que el tiempo ahorrado durante la depuración debido a los punteros de basura.

+3

No es la pregunta que estoy haciendo. Estoy de acuerdo, la seguridad es primordial, sin embargo, quiero saber si tendrá un impacto en el rendimiento. – Konrad

+0

Supongo que todos saben que habrá un impacto insignificante en el rendimiento debido a la copia de memoria. –

4

La regla es que las variables se deben establecer antes de que se usen.

No no tiene que inicializarlos explícitamente en la creación si sabe que los va a configurar en otro lugar antes de su uso.

Por ejemplo, el código siguiente es perfectamente válido:

int main (void) { 
    int a[1000]; 
    : : 
    for (int i =0; i < sizeof(a)/sizeof(*a); i++) 
     a[i] = i; 
    : : 
    // Now use a[whatever] here. 
    : : 
    return 0; 
} 

En ese caso, es un desperdicio para inicializar la matriz en el punto de su creación.

En cuanto a si hay una penalización de rendimiento, depende parcialmente de dónde se define su variable y parcialmente en el entorno de ejecución.

El estándar C garantiza que las variables definidas con duración de almacenamiento estático (ya sea a nivel de archivo o como estática en una función) se inicializan primero en un patrón de bits de todos los ceros y luego se establecen en sus respectivos valores inicializados.

Hace no mandato cómo se realiza ese segundo paso. Una forma típica es hacer que el compilador cree la variable inicializada y colocarla en el ejecutable para que se inicialice en virtud del hecho de que se cargó el ejecutable. Esto no tendrá ningún impacto en el rendimiento (para la inicialización, obviamente, tendrá algún impacto en la carga del programa).

Por supuesto, una implementación puede desear ahorrar espacio en el ejecutable e inicializar esas variables con código (antes de llamar a main). Este tendrá y tendrá un impacto en el rendimiento, pero es probable que sea minúsculo.

En cuanto a las variables con duración de almacenamiento automático (variables locales y similares), nunca se inicializan implícitamente a menos que se les asigne algo, por lo que también habrá una penalización de rendimiento para eso. Por "no inicializado de forma implícita", me refiero al segmento de código:

void x(void) { 
    int x[1000]; 
    ... 
} 

se traducirá en x [] con valores indeterminados. Pero desde:

void x(void) { 
    int x[1000] = {0}; 
} 

puede simplemente dar lugar a una operación de tipo memcpy 1000-número entero (probable memset más para ese caso), este será probable que sea rápido también. Solo debe tener en cuenta que la inicialización ocurrirá cada vez que se llama a esa función.

+0

Hmm, nunca he leído en el borrador del estándar sobre esta inicialización de dos fases. Todo lo que dice 6.7.8/10 "Si un objeto que tiene una duración de almacenamiento estática no se inicializa explícitamente, entonces ... - si tiene un tipo de puntero, se inicializa a un puntero nulo ...". ¿Pueden darme algunas pistas donde puedo encontrarlo diciendo que primero se inicializan con cero bits? Gracias amigo. –

+0

Incluso si dijera eso, creo que la implementación podría "como si" salir de ella, si así lo quisiera. –

+1

@JS, s5.1.2 Entornos de ejecución (c1x, n1362): se definen dos entornos de ejecución: independiente y alojado. En ambos casos, el inicio del programa se produce cuando el entorno de ejecución llama a una función C designada. Antes del inicio del programa, el área de almacenamiento que contiene todos los objetos con una duración de almacenamiento estática primero se borrará (todos los bytes se ponen a cero), luego los objetos se inicializarán (se establecerán en sus valores iniciales). La manera y el momento de tal inicialización no están especificados. La terminación del programa devuelve el control al entorno de ejecución. – paxdiablo

0

Para una gran variedad, un impacto en el rendimiento puede ser significativo.La inicialización de todas las variables por defecto en realidad no ofrece muchos beneficios. No es una solución para código incorrecto, además podría ocultar problemas reales que pueden ser captados por el compilador. Necesitas hacer un seguimiento del estado de todas las variables en toda su vida para hacer que tu código sea confiable de todos modos.

2

Medida!

#include <stdio.h> 
#include <time.h> 

int main(void) { 
    clock_t t0; 
    int k; 

    t0 = clock(); 
    for (k=0; k<1000000; k++) { 
    int a[1000]; 
    a[420] = 420; 
    } 
    printf("Without init: %f secs\n", (double)(clock() - t0)/CLOCKS_PER_SEC); 

    t0 = clock(); 
    for (k=0; k<1000000; k++) { 
    int a[1000] = {0}; 
    a[420] = 420; 
    } 
    printf(" With init: %f secs\n", (double)(clock() - t0)/CLOCKS_PER_SEC); 

    return 0; 
} 
 
$ gcc measure.c 
$ ./a.out 
Without init: 0.000000 secs 
    With init: 0.280000 secs 
$ gcc -O2 measure.c 
$ ./a.out 
Without init: 0.000000 secs 
    With init: 0.000000 secs 
+0

Está midiendo la inicialización de variables en la pila. Esto es diferente a un inicializador de variable global. Sin embargo, no está claro qué cartel está usando. – spoulson

+0

También inicializando a '0' no '\ 0' allí. No estoy seguro si eso marcaría una diferencia. – Konrad

+1

El punto es que si el cartel está preocupado con unos pocos nanosegundos, debería medir antes de determinar dónde mejorar el código. Estoy 99.9999% seguro de que no será la inicialización lo que importa. – pmg

7

Si la variable es global o estática, sus datos generalmente se almacenan textualmente en el ejecutable compilado. Por lo tanto, su char arrBuffer[1024] aumentará el tamaño del ejecutable en 1024 bytes. Inicializarlo asegurará que el archivo ejecutable contenga sus datos en lugar de los 0 predeterminados o lo que elija el compilador. Cuando se inicia el programa, no se requiere procesamiento para inicializar las variables.

Por otro lado, las variables en la pila, como las variables de función local no estáticas, no se almacenan en el ejecutable de la misma manera. En cambio, al ingresar la función, el espacio se asigna en la pila y una memcpy coloca los datos en la variable, lo que afecta el rendimiento.

0

Para responder a su pregunta: podría tener un impacto en el rendimiento. Es posible que un compilador pueda detectar que los valores de la matriz no se utilizaron y simplemente no los hace. Es posible.

Personalmente creo que esto es una cuestión de estilo personal. Estoy tentado de decir: déjalo sin inicializar, y usa una herramienta similar a Lint para decirte si lo estás utilizando sin inicializar, lo cual es seguramente un error (en lugar de usar el valor predeterminado y no ser dicho, que también es un error, pero uno silencioso).

0

Considero que es un mal consejo solicitar que todas las variables se inicialicen por defecto en el momento de la declaración. En la mayoría de los casos, es innecesario y conlleva una penalización de rendimiento.

Por ejemplo, a menudo usar el siguiente código para convertir un número en una cadena:

char s[24]; 
sprintf(s, "%d", int_val); 

no voy a escribir:

char s[24] = "\0"; 
sprintf(s, "%d", int_val); 

compiladores modernos son capaces de decir si una variable se usa sin inicializarse.

0

Sus variables deben inicializarse en un valor significativo. Establecer ciega e ingenuamente todo a cero no es mucho mejor que dejarlo sin inicializar. Puede hacer que el código no válido se bloquee, en lugar de comportarse de manera impredecible, pero no hará que el código sea correcto.

Si simplemente pone a cero la matriz al crearla solo para evitar variables sin inicializar, sigue siendo lógicamente sin inicializar. todavía no tiene un valor que sea significativo en su aplicación.

Si va a inicializar variables (y debería), déles valores que tengan sentido en su aplicación. ¿El resto de su código espera que la matriz sea cero inicialmente? Si es así, configúralo a cero. De lo contrario, establézcalo en algún otro valor significativo.

O si el resto de su código espera escribir en la matriz, sin leerlo primero, entonces déjelo sin inicializar.

0

Personalmente, estoy en contra de inicializar una matriz en created. Considere las siguientes dos piezas de código.

char buffer[1024] = {0}; 
for (int i = 0; i < 1000000; ++i) 
{ 
    // Use buffer 
} 

vs.

for (int i = 0; i < 1000000; ++i) 
{ 
    char buffer[1024] = {0}; 
    // Use buffer 
} 

En el primer ejemplo por qué molestarse tampón de inicialización desde la segunda vez alrededor de la memoria intermedia de bucle ya no es 0 inicializado? Mi uso del búfer debe funcionar sin que se inicialice para todos menos la primera iteración. Todo lo que hace la inicialización es consumir tiempo, hinchar el código y ocultar errores si normalmente solo paso por el ciclo una vez.

Si bien podría volver a factorizar el código como el segundo ejemplo, ¿realmente quiero cero inicializar un búfer dentro de un bucle si pudiera volver a escribir mi código para que no fuera necesario?

Sospecho que la mayoría de los compiladores de estos días tienen opciones para rellenar variables sin inicializar con valores de 0. Ejecutamos todas nuestras compilaciones de depuración de esta manera para ayudar a detectar el uso de variables no inicializadas, y en el modo de lanzamiento desactivamos la opción para que las variables realmente no estén inicializadas. Como dijo Sherwood Hu, algunos compiladores pueden inyectar código para ayudar a detectar el uso de variables no inicializadas.

Edit: En el código anterior estoy inicializando el buffer en el valor 0, (no el carácter '0'), lo que es equivalente a inicializarlo con '\ 0'.

Para aclarar aún más mi primer fragmento de código, imagine el siguiente ejemplo inventado.

char buffer[1024] = {0}; 
for (int i = 0; i < 1000000; ++i) 
{ 
    // Buffer is 0 initialized, so it is fine to call strlen 
    int len = strlen (buffer); 
    memset (buffer, 'a', 1024); 
} 

La primera vez a través del bucle la memoria intermedia se inicializa a 0, por lo strlen devolverá 0. La segunda vez a través del bucle de la memoria intermedia ya no se inicializa a 0, y de hecho no contiene un solo 0 carácter, por lo que el comportamiento de strlen no está definido.

Como ha acordado conmigo que si se inicializa el búfer, no es aconsejable mover el búfer dentro del bucle, y he demostrado que inicializarlo fuera del bucle no ofrece protección, ¿por qué inicializarlo?

+0

En primer lugar estoy inicializando nulo '\ 0' no '0', que es útil por una variedad de razones. En segundo lugar, la inicialización no genera el mismo resultado de rendimiento que la asignación. Su segundo fragmento de código, estoy de acuerdo, es desaconsejable. Tampoco entiendo su razonamiento sobre la "segunda vez". Realmente no creo que hayas explicado tu punto. ¿Podrías elaborarlo? – Konrad

+0

He editado mi publicación para aclarar uno de los ejemplos. –