c++ - utilizo - tipos de variables en programacion
¿Debería ser obligatoria la inicialización de variables locales? (17)
Los problemas de mantenimiento que causan los locales no inicializados (particularmente los indicadores) serán obvios para cualquiera que haya realizado un poco de mantenimiento o mejora de c / c ++, pero aún los veo y de vez en cuando escucho las implicaciones del rendimiento como justificación.
Es fácil demostrar en c que la inicialización redundante está optimizada:
$ less test.c
#include <stdio.h>
main()
{
#ifdef INIT_LOC
int a = 33;
int b;
memset(&b,66,sizeof(b));
#else
int a;
int b;
#endif
a = 0;
b = 0;
printf ("a = %i, b = %i/n", a, b);
}
$ gcc --version
gcc (GCC) 3.4.4 (cygming special, gdc 0.12, using dmd 0.125)
[No optimizado:]
$ gcc test.c -S -o no_init.s; gcc test.c -S -D INIT_LOC=1 -o init.s; diff no_in
it.s init.s
22a23,28
> movl $33, -4(%ebp)
> movl $4, 8(%esp)
> movl $66, 4(%esp)
> leal -8(%ebp), %eax
> movl %eax, (%esp)
> call _memset
33a40
> .def _memset; .scl 3; .type 32; .endef
[Optimizado:]
$ gcc test.c -O -S -o no_init.s; gcc test.c -O -S -D INIT_LOC=1 -o init.s; diff
no_init.s init.s
$
Entonces, ¿el rendimiento WRT bajo qué circunstancias es la inicialización variable obligatoria NO es una buena idea?
Si corresponde, no es necesario restringir las respuestas a c / c ++, pero tenga en cuenta el idioma / entorno (¡y las pruebas reproducibles son preferibles a la especulación!)
¿Actuación? ¿Hoy en día? Quizás cuando las CPU funcionaban a 10 mhz tenía sentido, pero hoy en día no es un problema. Siempre inicializarlos.
A veces, una variable se usa para "recolectar" el resultado de un bloque más largo de ifs / elses anidados ... En esos casos, a veces mantengo la variable sin inicializar, ya que una de las ramas condicionales debería inicializarla más adelante.
El truco es: si lo dejo sin inicializar al principio y luego hay un error en el bloque largo if / else para que la variable nunca se asigne, puedo ver ese error en Valgrind :-) que por supuesto requiere ejecutar frecuentemente el código ( idealmente las pruebas regulares) a través de Valgrind.
Algunas veces necesita una variable como marcador de posición (por ejemplo, usando las funciones ftime
), por lo que no tiene sentido inicializarlas antes de llamar a la función de inicialización.
Sin embargo, no estaría mal, en mi opinión, anotar el hecho de que está al tanto de las trampas, algo en el camino de
uninitialized time_t t;
time( &t );
Como ejemplo simple, ¿puedes determinar a qué se inicializará esto (C / C ++)?
bool myVar;
Tuvimos un problema en un producto que a veces dibujaba una imagen en la pantalla y otras no, por lo general dependiendo de con quién se fabricara la máquina. Resultó que en mi máquina se estaba inicializando en falso, y en una máquina de colegas se estaba inicializando en verdadero.
Como has demostrado con respecto al rendimiento, no hace la diferencia. El compilador (en compilaciones optimizadas) detectará si se escribe una variable local sin que se lea y eliminará el código a menos que tenga otros efectos secundarios.
Dicho esto: si inicializas cosas con declaraciones simples solo para asegurarte de que se inicializaron, está bien hacerlo ... Personalmente no lo hago, por una sola razón:
Bromea a los tipos que más tarde pueden mantener tu código y pensar que se requiere la inicialización. Ese pequeño foo = 0; aumentará la complejidad del código. Aparte de eso, es solo una cuestión de gusto.
Si no inicializa las variables mediante declaraciones complejas, puede tener un efecto secundario.
Por ejemplo:
float x = sqrt(0);
Puede ser optimizado por tu compilador si tienes suerte y trabajas con un compilador inteligente. Con un compilador no tan inteligente, puede dar como resultado una llamada de función costosa y no sincera porque sqrt puede, como efecto secundario, establecer la variable errno.
Si llama a las funciones que ha definido usted mismo, mi mejor opción es que el compilador siempre asuma que pueden tener efectos secundarios y no los optimice. Eso puede ser diferente si la función está en la misma unidad de traducción o si tiene activada la optimización de todo el programa.
Creo que en la mayoría de los casos es una mala idea inicializar variables con un valor predeterminado, porque simplemente oculta errores que se encuentran fácilmente con variables no inicializadas. Si olvida obtener y establecer el valor real, o eliminar el código get por accidente, probablemente nunca lo note porque 0 en muchos casos es un valor razonable. En general, es mucho más fácil activar esos errores con un valor >> 0.
Por ejemplo:
void func(int n)
{
int i = 0;
... // Many lines of code
for (;i < n; i++)
do_something(i);
Después de un tiempo, agregará otras cosas.
void func(int n)
{
int i = 0;
for (i = 0; i < 3; i++)
do_something_else(i);
... // Many lines of code
for (;i < n; i++)
do_something(i);
Ahora su segundo ciclo no comenzará con 0, pero con 3, dependiendo de lo que haga la función, puede ser muy difícil de encontrar, incluso hay un error.
No estoy seguro de si es necesario "hacerlos obligatorios", pero personalmente creo que siempre es mejor inicializar las variables. Si el propósito de la aplicación es ser lo más ajustado posible, entonces C / C ++ está abierto para ese fin. Sin embargo, creo que muchos de nosotros hemos sido quemados una o dos veces al no inicializar una variable y suponiendo que contiene un valor válido (por ejemplo, un puntero) cuando en realidad no es así. Un puntero con una dirección de cero es mucho más fácil de verificar que si tiene basura al azar de los últimos contenidos de la memoria en esa ubicación en particular. Creo que en la mayoría de los casos, ya no es una cuestión de rendimiento, sino una cuestión de claridad y seguridad.
Respuesta corta: declare la variable lo más cerca posible del primer uso e inicialice a "cero" si aún lo necesita.
Respuesta larga: si declara una variable al inicio de una función y no la usa hasta más tarde, debe reconsiderar su ubicación de la variable en un alcance lo más local posible. Por lo general, puede asignarle el valor necesario de inmediato.
Si debe declararlo sin inicializar porque se asigna en un condicional, o se pasa por referencia y se le asigna, inicializarlo en un valor nulo equivalente es una buena idea. El compilador a veces puede guardarlo si compila bajo-Pared, ya que avisará si lee de una variable antes de inicializarla. Sin embargo, no le advierte si pasa a una función.
Si juega a lo seguro y lo establece en un equivalente nulo, no ha hecho daño si la función a la que lo pasa lo sobrescribe. Sin embargo, si la función a la que le pasa utiliza el valor, se puede garantizar que fallará una afirmación (si tiene una), o al menos segfaulting en el segundo que use un objeto nulo. La inicialización aleatoria puede hacer todo tipo de cosas malas, incluido el "trabajo".
Siempre inicialice las variables locales a cero como mínimo. Como viste, no hay un rendimiento real.
int i = 0;
struct myStruct m = {0};
Básicamente estás agregando 1 o 2 instrucciones de ensamblaje, si eso. De hecho, muchos C tiempos de ejecución harán esto por ti en una compilación de "Liberación" y no estarás cambiando nada.
Pero debes iniciarlo porque ahora tendrás esa garantía.
Una razón para no inicializar tiene que ver con la depuración. Algunos tiempos de ejecución, ej. MS CRT, inicializará la memoria con patrones predeterminados y documentados que puede identificar. Entonces, cuando está vertiendo a través de la memoria, puede ver que la memoria no está inicializada y que no se ha usado y reseteado. Eso puede ser útil en la depuración. Pero eso es durante la depuración.
Debería ser principalmente obligatorio. La razón de esto no tiene nada que ver con el rendimiento sino con el peligro de usar una variable unitaria. Sin embargo, hay casos en que simplemente se ve ridículo. Por ejemplo, he visto:
struct stat s;
s.st_dev = -1;
s.st_ino = -1;
s.st_mode = S_IRWXU;
s.st_nlink = 0;
s.st_size = 0;
// etc...
s.st_st_ctime = -1;
if(stat(path, &s) != 0) {
// handle error
return;
}
¿WTF?
Tenga en cuenta que estamos solucionando el error de inmediato, por lo que no hay dudas sobre qué sucede si falla la estadística.
Este es un gran ejemplo de optimización prematura es la raíz de todo mal
La cita completa es:
No hay duda de que el grial de la eficiencia conduce al abuso. Los programadores pierden una enorme cantidad de tiempo pensando o preocupándose por la velocidad de las partes no críticas de sus programas, y estos intentos de eficiencia en realidad tienen un fuerte impacto negativo cuando se consideran la depuración y el mantenimiento . Deberíamos olvidarnos de pequeñas eficiencias, digamos aproximadamente el 97% del tiempo: la optimización prematura es la raíz de todo mal. Sin embargo, no deberíamos dejar pasar nuestras oportunidades en ese crítico 3%. Un buen programador no se dejará llevar por la complacencia con tal razonamiento; será prudente que mire cuidadosamente el código crítico; pero solo después de que ese código ha sido identificado.
Esto vino de Donald Knuth . ¿A quién vas a creer ... tus colegas o Knuth?
Sé dónde está mi dinero ...
Para volver a la pregunta original: "¿Deberíamos MANDAR la inicialización?"
Lo diría así:
Las variables deben inicializarse, excepto en una situación en la que se pueda demostrar que hay una ganancia de rendimiento significativa que se puede lograr al no inicializarse. Ven armado con números duros ...
Permítanme contarles una historia sobre un producto en el que trabajé en 1992 y posteriormente que, a los fines de esta historia, llamaremos a Stackrobat. Me asignaron un error que hizo que la aplicación fallara en la Mac, pero no en Windows, ah, y el error no era reproducible de manera confiable. Le tomó a QA la mejor parte de una semana obtener una receta que funcionó tal vez 1 de cada 10 veces.
Fue un infierno rastrear la causa raíz, ya que el choque real ocurrió mucho después de la acción que lo hizo.
En última instancia, lo rastreé escribiendo un generador de perfiles de código personalizado para el compilador. El compilador inyectaría llamadas a las funciones global prof_begin () y prof_end () y usted sería libre de implementarlas. Escribí un generador de perfiles que tomó la dirección de retorno de la pila, encontré la instrucción de creación del marco de pila, localicé el bloque en la pila que representaba a los locales para la función y los cubrí con una sabrosa capa de basura que causaría un error de bus si alguno el elemento fue desreferenciado.
Esto captó algo así como una media docena de errores de punteros utilizados antes de la inicialización, incluido el error que estaba buscando.
Lo que sucedió fue que la mayoría de las veces la pila tenía valores que aparentemente eran benignos si se desreferenciaba. Otras veces, los valores harían que la aplicación se disparara, sacando la aplicación en algún momento mucho más tarde.
Pasé más de dos semanas tratando de encontrar este error.
Lección: inicializa a tus lugareños. Si alguien te critica el rendimiento, muéstrale este comentario y diles que preferirías pasar dos semanas ejecutando el código de perfil y solucionando los cuellos de botella en lugar de tener que buscar errores como este. Las herramientas de depuración y las comprobaciones de montón han mejorado mucho desde que tuve que hacer esto, pero, francamente, mejoraron para compensar errores de prácticas deficientes como esta.
A menos que esté ejecutando un sistema pequeño (integrado, etc.), la inicialización de los locales debería ser casi gratuita. Las instrucciones MOVE / LOAD son muy, muy rápidas. Escriba el código para que sea sólido y fácil de mantener primero. Refactorizarlo para que sea el segundo en rendimiento.
Solo una observación secundaria. Las inicializaciones solo se optimizan FÁCILMENTE en los tipos primitivos o cuando las funciones const las asignan.
a = foo ();
a = foo2 ();
No se puede optimizar fácilmente porque foo puede tener efectos secundarios.
También las asignaciones de montón antes del tiempo pueden dar como resultado grandes éxitos de rendimiento. Tome un código como
void foo(int x)
{
ClassA * instance = new ClassA ();
// ... hacer algo que no esté relacionado con "instancia" ... if (x> 5) {
delete instance;
return;
}
// ... hacer algo que use instancia
}
En ese caso, simplemente declare la instancia justo cuando la usará, e inicialícela solo allí. Y no. El compilador no puede optimizar eso para usted, ya que el constructor puede tener efectos secundarios que cambiarían el código de la reordenación.
editar: No uso la función de listado de códigos: P
Sí: siempre inicialice sus variables a menos que tenga una muy buena razón para no hacerlo. Si mi código no requiere un valor inicial particular, a menudo inicializaré una variable a un valor que garantizará un error evidente si el código que sigue está roto.
En C / C ++ estoy totalmente de acuerdo contigo.
En Perl, cuando creo una variable, automáticamente se pone a un valor predeterminado.
my ($val1, $val2, $val3, $val4);
print $val1, "/n";
print $val1 + 1, "/n";
print $val2 + 2, "/n";
print $val3 = $val3 . ''Hello, SO!'', "/n";
print ++$val4 +4, "/n";
Todos están listos para undef inicialmente. Undef es un valor falso y un marcador de posición. Debido al tipado dinámico si le agrego un número, asume que mi variable es un número y reemplaza undef con el valor falso eqivilente 0. Si hago operaciones de cadena una versión falsa de una cadena es una cadena vacía, y eso se pone automáticamente sustituido.
[jeremy@localhost Code]$ ./undef.pl
1
2
Hello, SO!
5
Entonces, para Perl, al menos, declare temprano y no se preocupe. Especialmente porque la mayoría de los programas tienen muchas variables. Utiliza menos líneas y se ve más limpio sin una inicialización explícita.
my($x, $y, $z);
:-)
my $x = 0;
my $y = 0;
my $z = 0;
Esto pertenece solo a C ++, pero hay una distinción definida entre los dos métodos. Supongamos que tiene una clase MyStuff
y desea inicializarla por otra clase. Podrías hacer algo como:
// Initialize MyStuff instance y
// ...
MyStuff x = y;
// ...
Lo que esto realmente hace es llamar al constructor de copias de x. Es lo mismo que:
MyStuff x(y);
Esto es diferente a este código:
MyStuff x; // This calls the MyStuff default constructor.
x = y; // This calls the MyStuff assignment operator.
Por supuesto, se llama un código completamente diferente cuando se realiza una copia de construcción en lugar de una construcción o asignación predeterminada. Además, una única llamada al constructor de copias probablemente sea más eficiente que la construcción seguida de la asignación.
Si crees que una inicialización es redundante, lo es. Mi objetivo es escribir código que sea tan humanamente legible como sea posible. La inicialización innecesaria confunde al lector futuro.
Los compiladores C se están volviendo bastante buenos para capturar el uso de variables unificadas, por lo que el peligro de eso ahora es mínimo.
No lo olvides, al hacer una inicialización "falsa", cambias un peligro: colisionar con el uso de basura (lo que lleva a un error que es muy fácil de encontrar y corregir) en otro - programa que toma una acción incorrecta basada en el valor falso (que conduce a un error que es muy difícil de encontrar). La elección depende de la aplicación. Para algunos, es fundamental que nunca se bloquee. Para la mayoría, es mejor detectar el error lo antes posible.