Siempre inicialice sus variables
La diferencia entre las situaciones que está considerando es que el caso sin inicialización da como resultado un comportamiento indefinido , mientras que el caso en el que se tomó el tiempo para inicializar crea un error bien definido y determinista . No puedo enfatizar cuán extremadamente diferentes son estos dos casos.
Considere un ejemplo hipotético que puede haberle sucedido a un empleado hipotético en un programa de simulaciones hipotéticas. Este equipo hipotético estaba hipotéticamente tratando de hacer una simulación determinista para demostrar que el producto que hipotéticamente vendían satisfacía las necesidades.
Bien, me detendré con la palabra inyecciones. Creo que entiendes el punto ;-)
En esta simulación, había cientos de variables no inicializadas. Un desarrollador ejecutó valgrind en la simulación y notó que había varios errores de "ramificación en valor no inicializado". "Hmm, parece que eso podría causar no determinismo, haciendo que sea difícil repetir las pruebas cuando más lo necesitamos". El desarrollador fue a la administración, pero la administración estaba en un horario muy apretado y no podía ahorrar recursos para rastrear este problema. "Terminamos inicializando todas nuestras variables antes de usarlas. Tenemos buenas prácticas de codificación".
Unos meses antes de la entrega final, cuando la simulación está en modo de abandono completo, y todo el equipo está corriendo para terminar todo lo que la administración prometió con un presupuesto que, como todos los proyectos financiados, era demasiado pequeño. Alguien notó que no podían probar una característica esencial porque, por alguna razón, el simulador determinista no se comportaba de manera determinista para depurar.
Es posible que todo el equipo se haya detenido y haya pasado la mayor parte de 2 meses peinando toda la base de código de simulación arreglando errores de valor no inicializados en lugar de implementar y probar características. No es necesario decir que el empleado se saltó los "Te dije" y comenzó a ayudar a otros desarrolladores a comprender cuáles son los valores no inicializados. Curiosamente, los estándares de codificación se cambiaron poco después de este incidente, alentando a los desarrolladores a que siempre inicialicen sus variables.
Y este es el disparo de advertencia. Esta es la bala que rozó tu nariz. El problema real es mucho, mucho, mucho, mucho más insidioso de lo que imaginas.
El uso de un valor no inicializado es "comportamiento indefinido" (a excepción de algunos casos de esquina como char
). El comportamiento indefinido (o UB para abreviar) es tan loco y completamente malo para usted, que nunca debería creer que es mejor que la alternativa. A veces puede identificar que su compilador particular define el UB, y luego es seguro de usar, pero de lo contrario, el comportamiento indefinido es "cualquier comportamiento que el compilador siente". Puede hacer algo que llamarías "cuerdo" como tener un valor no especificado. Puede emitir códigos de operación no válidos, lo que puede causar que su programa se corrompa. Puede desencadenar una advertencia en el momento de la compilación, o incluso el compilador puede considerarlo como un error absoluto.
O puede que no haga nada
Mi canario en la mina de carbón para UB es un caso de un motor SQL sobre el que leí. Perdóname por no vincularlo, no he podido encontrar el artículo nuevamente. Hubo un problema de desbordamiento del búfer en el motor SQL cuando pasó un tamaño de búfer más grande a una función, pero solo en una versión particular de Debian. El error fue debidamente registrado y explorado. La parte divertida fue: se comprobó el desbordamiento del búfer . Había código para manejar el desbordamiento del búfer en su lugar. Se parecía a esto:
// move the pointers properly to copy data into a ring buffer.
char* putIntoRingBuffer(char* begin, char* end, char* get, char*put, char* newData, unsigned int dataLength)
{
// If dataLength is very large, we might overflow the pointer
// arithmetic, and end up with some very small pointer number,
// causing us to fail to realize we were trying to write past the
// end. Check this before we continue
if (put + dataLength < put)
{
RaiseError("Buffer overflow risk detected");
return 0;
}
...
// typical ring-buffer pointer manipulation followed...
}
He agregado más comentarios en mi versión, pero la idea es la misma. Si se put + dataLength
envuelve, será más pequeño que el put
puntero (tenían controles de tiempo de compilación para asegurarse de que int sin firmar era del tamaño de un puntero, para los curiosos). Si esto sucede, sabemos que los algoritmos de buffer de anillo estándar pueden confundirse con este desbordamiento, por lo que devolvemos 0. ¿ O sí?
Como resultado, el desbordamiento en los punteros no está definido en C ++. Debido a que la mayoría de los compiladores tratan los punteros como enteros, terminamos con comportamientos típicos de desbordamiento de enteros, que resultan ser el comportamiento que queremos. Sin embargo, este es un comportamiento indefinido, lo que significa que el compilador puede hacer lo que quiera.
En el caso de este error, Debian pasó a optar por utilizar una nueva versión de gcc que ninguno de los otros grandes sabores de Linux había actualizado en sus notas de producción. Esta nueva versión de gcc tenía un optimizador de código muerto más agresivo. El compilador vio el comportamiento indefinido y decidió que el resultado de la if
declaración sería "cualquier cosa que haga mejor la optimización del código", que fue una traducción absolutamente legal de UB. En consecuencia, asumió que, dado que ptr+dataLength
nunca puede estar por debajo ptr
sin un desbordamiento del puntero UB, la if
instrucción nunca se activará y optimizó la verificación de desbordamiento del búfer.
El uso de UB "sano" en realidad causó que un producto SQL importante tuviera un exploit de desbordamiento de búfer que tenía un código escrito para evitar.
Nunca confíe en un comportamiento indefinido. Siempre.
bytes_read
no se cambia (por lo que se mantiene en cero), ¿por qué se supone que esto es un error? El programa aún podría continuar de una manera sensata siempre que no espere implícitamentebytes_read!=0
después. Así que está bien, los desinfectantes no se quejan. Por otro lado, cuandobytes_read
no se inicializa de antemano, el programa no podrá continuar de una manera sensata, por lo que no inicializarbytes_read
realmente introduce un error que no estaba allí de antemano.