¿Es la variable local no inicializada el generador de números aleatorios más rápido?
Sé que la variable local no inicializada tiene un comportamiento indefinido ( UB ), y también el valor puede tener representaciones trampa que pueden afectar el funcionamiento posterior, pero a veces quiero usar el número aleatorio solo para la representación visual y no los usaré más en otra parte de programa, por ejemplo, establece algo con color aleatorio en un efecto visual, por ejemplo:
void updateEffect(){
for(int i=0;i<1000;i++){
int r;
int g;
int b;
star[i].setColor(r%255,g%255,b%255);
bool isVisible;
star[i].setVisible(isVisible);
}
}
¿Es tan rápido que
void updateEffect(){
for(int i=0;i<1000;i++){
star[i].setColor(rand()%255,rand()%255,rand()%255);
star[i].setVisible(rand()%2==0?true:false);
}
}
¿Y también más rápido que otros generadores de números aleatorios?
Como otros han señalado, este es un comportamiento indefinido (UB).
En la práctica, (probablemente) realmente (más o menos) funcionará. La lectura de un registro no inicializado en arquitecturas x86[-64] de hecho producirá resultados basura y probablemente no hará nada malo (a diferencia de, por ejemplo, Itanium, donde los registros se pueden marcar como no válidos , por lo que las lecturas propagan errores como NaN).
Sin embargo, hay dos problemas principales:
No será particularmente aleatorio. En este caso, estás leyendo de la pila, por lo que obtendrás todo lo que había allí anteriormente. Que podría ser efectivamente aleatoria, completamente estructurada, la contraseña que ingresaste hace diez minutos o la receta de galletas de tu abuela.
Es una mala práctica ('B' mayúscula) permitir que cosas como esta se introduzcan en su código. Técnicamente, el compilador podría insertar
reformat_hdd();
cada vez que lea una variable indefinida. No lo hará , pero no deberías hacerlo de todos modos. No hagas cosas inseguras. Cuantas menos excepciones haga, más seguro estará de cometer errores accidentales todo el tiempo.
El problema más apremiante con UB es que hace que todo el comportamiento de su programa sea indefinido. Los compiladores modernos pueden usar esto para eludir grandes porciones de su código o incluso retroceder en el tiempo . Jugar con UB es como un ingeniero victoriano desmantelando un reactor nuclear activo. Hay un millón de cosas que pueden salir mal y probablemente no conozcas la mitad de los principios subyacentes o la tecnología implementada. Puede que esté bien, pero aun así no deberías permitir que suceda. Mire las otras buenas respuestas para obtener más detalles.
Además te despediría.
Permítanme decir esto claramente: no invocamos comportamientos indefinidos en nuestros programas . Nunca es una buena idea, punto. Existen raras excepciones a esta regla; por ejemplo, si es un implementador de bibliotecas que implementa offsetof . Si su caso cae dentro de una excepción de este tipo, probablemente ya lo sepa. En este caso sabemos que el uso de variables automáticas no inicializadas es un comportamiento indefinido .
Los compiladores se han vuelto muy agresivos con las optimizaciones en torno al comportamiento indefinido y podemos encontrar muchos casos en los que el comportamiento indefinido ha provocado fallas de seguridad. El caso más infame es probablemente la eliminación de la verificación del puntero nulo del kernel de Linux que menciono en mi respuesta al error de compilación de C++. donde una optimización del compilador en torno a un comportamiento indefinido convirtió un bucle finito en uno infinito.
Podemos leer Optimizaciones peligrosas y pérdida de causalidad del CERT ( vídeo ) que dice, entre otras cosas:
Cada vez más, los escritores de compiladores aprovechan los comportamientos indefinidos en los lenguajes de programación C y C++ para mejorar las optimizaciones.
Con frecuencia, estas optimizaciones interfieren con la capacidad de los desarrolladores para realizar análisis de causa-efecto en su código fuente, es decir, analizar la dependencia de los resultados posteriores de los resultados anteriores.
En consecuencia, estas optimizaciones eliminan la causalidad en el software y aumentan la probabilidad de fallas, defectos y vulnerabilidades del software.
Específicamente con respecto a valores indeterminados, el informe de defectos estándar de C 451: Inestabilidad de variables automáticas no inicializadas es una lectura interesante. Aún no se ha resuelto, pero introduce el concepto de valores inestables , lo que significa que la indeterminación de un valor puede propagarse a través del programa y puede tener diferentes valores indeterminados en diferentes puntos del programa.
No conozco ningún ejemplo en el que esto suceda, pero en este momento no podemos descartarlo.
Ejemplos reales, no el resultado que esperas
Es poco probable que obtenga valores aleatorios. Un compilador podría optimizar el bucle por completo. Por ejemplo, con este caso simplificado:
void updateEffect(int arr[20]){
for(int i=0;i<20;i++){
int r ;
arr[i] = r ;
}
}
clang lo optimiza ( véalo en vivo ):
updateEffect(int*): # @updateEffect(int*)
retq
o tal vez obtener todos ceros, como en este caso modificado:
void updateEffect(int arr[20]){
for(int i=0;i<20;i++){
int r ;
arr[i] = r%255 ;
}
}
verlo en vivo :
updateEffect(int*): # @updateEffect(int*)
xorps %xmm0, %xmm0
movups %xmm0, 64(%rdi)
movups %xmm0, 48(%rdi)
movups %xmm0, 32(%rdi)
movups %xmm0, 16(%rdi)
movups %xmm0, (%rdi)
retq
Ambos casos son formas perfectamente aceptables de comportamiento indefinido.
Tenga en cuenta que si estamos en un Itanium podríamos terminar con un valor de trampa :
[...] si el registro contiene un valor especial que no es nada, la lectura del registro es trampa excepto por algunas instrucciones[...]
Otras notas importantes
Es interesante observar la variación entre gcc y clang observada en el proyecto UB Canaries sobre qué tan dispuestos están a aprovechar el comportamiento indefinido con respecto a la memoria no inicializada. El artículo señala ( énfasis mío ):
Por supuesto, debemos ser completamente claros con nosotros mismos en que tal expectativa no tiene nada que ver con el estándar del lenguaje sino con lo que hace un compilador en particular, ya sea porque los proveedores de ese compilador no están dispuestos a explotar esa UB o simplemente porque todavía no han conseguido explotarlo . Cuando no existe una garantía real por parte del proveedor del compilador, nos gusta decir que las UB aún no explotadas son bombas de tiempo : están esperando a explotar el próximo mes o el próximo año cuando el compilador se vuelva un poco más agresivo.
Como Matthieu M. señala, lo que todo programador de C debe saber sobre el comportamiento indefinido n.° 2/3 también es relevante para esta pregunta. Dice entre otras cosas ( énfasis mío ):
Lo importante y aterrador es darse cuenta de que casi cualquier optimización basada en un comportamiento indefinido puede comenzar a activarse en un código defectuoso en cualquier momento en el futuro . La inserción en línea, el desenrollado de bucles, la promoción de memoria y otras optimizaciones seguirán mejorando, y una parte importante de su razón de existir es exponer optimizaciones secundarias como las anteriores.
Para mí, esto es profundamente insatisfactorio, en parte porque inevitablemente terminan culpando al compilador, pero también porque significa que enormes cuerpos de código C son minas terrestres a punto de explotar.
Para completar, probablemente debería mencionar que las implementaciones pueden optar por hacer que el comportamiento indefinido esté bien definido, por ejemplo, gcc permite juegos de palabras mediante uniones , mientras que en C++ esto parece un comportamiento indefinido . Si este es el caso, la implementación debería documentarlo y, por lo general, esto no será portátil.