Je sais que la variable locale non initialisée est un comportement indéfini ( UB ), et la valeur peut également avoir des représentations d'interruption qui peuvent affecter le fonctionnement ultérieur, mais parfois je veux utiliser le nombre aléatoire uniquement pour la représentation visuelle et ne les utiliserai pas dans d'autres parties de programme, par exemple, définir quelque chose avec une couleur aléatoire dans un effet visuel, par exemple:
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);
}
}
est-ce plus rapide 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);
}
}
et aussi plus rapide que les autres générateurs de nombres aléatoires?
Réponses:
Comme d'autres l'ont noté, il s'agit d'un comportement indéfini (UB).
En pratique, cela fonctionnera (probablement) en fait (en quelque sorte). La lecture à partir d'un registre non initialisé sur les architectures x86 [-64] produira effectivement des résultats inutiles, et ne fera probablement rien de mal (contrairement à par exemple Itanium, où les registres peuvent être marqués comme invalides , de sorte que les lectures propagent des erreurs comme NaN).
Il existe cependant deux problèmes principaux:
Ce ne sera pas particulièrement aléatoire. Dans ce cas, vous lisez dans la pile, vous obtiendrez donc tout ce qui s'y trouvait auparavant. Ce qui pourrait être effectivement aléatoire, complètement structuré, le mot de passe que vous avez entré il y a dix minutes ou la recette de biscuits de votre grand-mère.
C'est une mauvaise pratique (majuscule «B») de laisser des choses comme celle-ci se glisser dans votre code. Techniquement, le compilateur peut insérer
reformat_hdd();
chaque fois que vous lisez une variable non définie. Ce ne sera pas le cas , mais vous ne devriez pas le faire de toute façon. Ne faites pas de choses dangereuses. Moins vous faites d'exceptions, plus vous êtes à l'abri des erreurs accidentelles en tout temps.Le problème le plus urgent avec UB est qu'il rend le comportement de l'ensemble de votre programme indéfini. Les compilateurs modernes peuvent l'utiliser pour éluder d'énormes portions de votre code ou même remonter dans le temps . Jouer avec UB, c'est comme un ingénieur victorien démanteler un réacteur nucléaire sous tension. Il y a des millions de choses qui tournent mal, et vous ne connaîtrez probablement pas la moitié des principes sous-jacents ou de la technologie mise en œuvre. C'est peut- être bien, mais vous ne devriez toujours pas laisser cela se produire. Regardez les autres belles réponses pour plus de détails.
Aussi, je te virerais.
la source
unreachable()
et supprimer la moitié de votre programme. Cela se produit également dans la pratique. Je crois que ce comportement a complètement neutralisé le RNG dans certaines distributions Linux .; La plupart des réponses à cette question semblent supposer qu'une valeur non initialisée se comporte comme une valeur. C'est faux.Permettez-moi de dire ceci clairement: nous n'invoquons pas de comportement indéfini dans nos programmes . Ce n'est jamais une bonne idée, point final. Il existe de rares exceptions à cette règle; par exemple, si vous êtes un implémenteur de bibliothèque implémentant offsetof . Si votre cas relève d'une telle exception, vous le savez probablement déjà. Dans ce cas, nous savons que l'utilisation de variables automatiques non initialisées est un comportement non défini .
Les compilateurs sont devenus très agressifs avec des optimisations concernant un comportement indéfini et nous pouvons trouver de nombreux cas où un comportement indéfini a conduit à des failles de sécurité. Le cas le plus tristement célèbre est probablement la suppression de la vérification du pointeur nul du noyau Linux que je mentionne dans ma réponse au bogue de compilation C ++? où une optimisation du compilateur autour d'un comportement non défini a transformé une boucle finie en boucle infinie.
Nous pouvons lire les Optimisations dangereuses et la perte de causalité du CERT ( vidéo ) qui disent, entre autres:
En ce qui concerne spécifiquement les valeurs indéterminées, le rapport de défaut standard C 451: Instabilité des variables automatiques non initialisées rend la lecture intéressante. Il n'a pas encore été résolu mais introduit le concept de valeurs bancales qui signifie que l'indétermination d'une valeur peut se propager à travers le programme et peut avoir différentes valeurs indéterminées à différents points du programme.
Je ne connais aucun exemple où cela se produit, mais à ce stade, nous ne pouvons pas l'exclure.
De vrais exemples, pas le résultat que vous attendez
Il est peu probable que vous obteniez des valeurs aléatoires. Un compilateur pourrait optimiser complètement la boucle. Par exemple, avec ce cas simplifié:
clang l'optimise ( voir en direct ):
ou peut-être obtenir tous les zéros, comme avec ce cas modifié:
voir en direct :
Ces deux cas sont des formes parfaitement acceptables de comportement indéfini.
Remarque, si nous sommes sur un Itanium, nous pourrions nous retrouver avec une valeur de piège :
Autres notes importantes
Il est intéressant de noter la variance entre gcc et clang notée dans le projet UB Canaries sur leur volonté de profiter d'un comportement indéfini par rapport à la mémoire non initialisée. L'article note ( soulignement le mien ):
Comme Matthieu M. le souligne, ce que tout programmeur C devrait savoir sur le comportement indéfini # 2/3 est également pertinent pour cette question. Il dit entre autres (c'est moi qui souligne ):
Par souci d'exhaustivité, je devrais probablement mentionner que les implémentations peuvent choisir de rendre le comportement indéfini bien défini, par exemple, gcc autorise la punition de type via les unions tandis qu'en C ++, cela semble être un comportement indéfini . Si tel est le cas, l'implémentation doit le documenter et ce ne sera généralement pas portable.
la source
Non, c'est terrible.
Le comportement de l'utilisation d'une variable non initialisée n'est pas défini à la fois en C et C ++, et il est très peu probable qu'un tel schéma ait des propriétés statistiques souhaitables.
Si vous voulez un générateur de nombres aléatoires "rapide et sale", alors
rand()
c'est votre meilleur pari. Dans sa mise en œuvre, tout ce qu'il fait est une multiplication, une addition et un module.Le générateur le plus rapide que je connaisse vous oblige à utiliser a
uint32_t
comme type de variable pseudo-aléatoireI
, et à utiliserI = 1664525 * I + 1013904223
pour générer des valeurs successives. Vous pouvez choisir n'importe quelle valeur initiale de
I
(appelée la graine ) qui vous convient. De toute évidence, vous pouvez coder cela en ligne. L'enveloppe standard garantie d'un type non signé agit comme module. (Les constantes numériques sont choisies à la main par ce remarquable programmeur scientifique Donald Knuth.)la source
rand()
n'est pas apte à l'usage et devrait être entièrement déconseillé, à mon avis. Ces jours-ci, vous pouvez télécharger gratuitement des générateurs de nombres aléatoires sous licence et largement supérieurs (par exemple Mersenne Twister) qui sont presque aussi rapides avec la plus grande facilité, il n'est donc vraiment pas nécessaire de continuer à utiliser le très défectueuxrand()
Bonne question!
Undefined ne signifie pas qu'il est aléatoire. Pensez-y, les valeurs que vous obtiendriez dans les variables globales non initialisées y ont été laissées par le système ou vos / autres applications en cours d'exécution. Selon ce que fait votre système avec la mémoire qui n'est plus utilisée et / ou le type de valeurs générées par le système et les applications, vous pouvez obtenir:
Les valeurs que vous obtiendrez dépendent entièrement des valeurs non aléatoires laissées par le système et / ou les applications. Ainsi, en effet, il y aura du bruit (à moins que votre système n'efface plus la mémoire), mais le pool de valeurs dont vous tirerez ne sera en aucun cas aléatoire.
Les choses empirent pour les variables locales car elles proviennent directement de la pile de votre propre programme. Il y a de très bonnes chances que votre programme écrive réellement ces emplacements de pile pendant l'exécution d'un autre code. J'estime que les chances de chance dans cette situation sont très faibles, et un changement de code «aléatoire» que vous effectuez tente cette chance.
Lisez à propos de l' aléatoire . Comme vous le verrez, le caractère aléatoire est une propriété très spécifique et difficile à obtenir. C'est une erreur courante de penser que si vous prenez simplement quelque chose qui est difficile à suivre (comme votre suggestion), vous obtiendrez une valeur aléatoire.
la source
Beaucoup de bonnes réponses, mais permettez-moi d'en ajouter une autre et d'insister sur le fait que dans un ordinateur déterministe, rien n'est aléatoire. Cela est vrai à la fois pour les nombres produits par un pseudo-RNG et pour les nombres apparemment "aléatoires" trouvés dans les zones de mémoire réservées aux variables locales C / C ++ sur la pile.
MAIS ... il y a une différence cruciale.
Les nombres générés par un bon générateur pseudo-aléatoire ont les propriétés qui les rendent statistiquement similaires à des tirages vraiment aléatoires. Par exemple, la distribution est uniforme. La durée du cycle est longue: vous pouvez obtenir des millions de nombres aléatoires avant que le cycle ne se répète. La séquence n'est pas autocorrélée: par exemple, vous ne commencerez pas à voir des motifs étranges émerger si vous prenez tous les 2, 3 ou 27 nombres, ou si vous regardez des chiffres spécifiques dans les nombres générés.
En revanche, les nombres "aléatoires" laissés sur la pile n'ont aucune de ces propriétés. Leurs valeurs et leur caractère aléatoire apparent dépendent entièrement de la façon dont le programme est construit, comment il est compilé et comment il est optimisé par le compilateur. À titre d'exemple, voici une variante de votre idée en tant que programme autonome:
Lorsque je compile ce code avec GCC sur une machine Linux et que je l'exécute, il s'avère être plutôt désagréablement déterministe:
Si vous regardiez le code compilé avec un désassembleur, vous pourriez reconstruire ce qui se passait, en détail. Le premier appel à notrandom () a utilisé une zone de la pile qui n'était pas utilisée par ce programme auparavant; qui sait ce qu'il y avait dedans. Mais après cet appel à notrandom (), il y a un appel à printf () (que le compilateur GCC optimise en fait à un appel à putchar (), mais peu importe) et qui écrase la pile. Ainsi, la fois suivante et suivante, lorsque notrandom () est appelé, la pile contiendra des données périmées de l'exécution de putchar (), et puisque putchar () est toujours appelé avec les mêmes arguments, ces données périmées seront toujours les mêmes, aussi.
Il n'y a donc absolument rien aléatoire dans ce comportement, et les nombres ainsi obtenus n'ont aucune des propriétés souhaitables d'un générateur de nombres pseudo-aléatoires bien écrit. En fait, dans la plupart des scénarios réels, leurs valeurs seront répétitives et fortement corrélées.
En effet, comme d'autres, j'envisagerais également sérieusement de licencier quelqu'un qui a tenté de faire passer cette idée comme un "RNG haute performance".
la source
/dev/random
sont souvent issues de telles sources matérielles, et sont en fait du «bruit quantique», c'est-à-dire vraiment imprévisible au meilleur sens physique du terme.Un comportement indéfini signifie que les auteurs des compilateurs sont libres d'ignorer le problème car les programmeurs n'auront jamais le droit de se plaindre quoi qu'il arrive.
Alors qu'en théorie, lorsque vous entrez dans un terrain UB, tout peut arriver (y compris un démon volant de votre nez ), ce qui signifie normalement que les auteurs du compilateur ne s'en soucient pas et, pour les variables locales, la valeur sera tout ce qui est dans la mémoire de la pile à ce moment-là .
Cela signifie également que souvent le contenu sera "étrange" mais fixe ou légèrement aléatoire ou variable mais avec un modèle évident clair (par exemple, augmentant les valeurs à chaque itération).
Pour sûr, vous ne pouvez pas vous attendre à ce qu'il soit un générateur aléatoire décent.
la source
Le comportement indéfini n'est pas défini. Cela ne signifie pas que vous obtenez une valeur indéfinie, cela signifie que le programme peut faire n'importe quoi et toujours répondre aux spécifications du langage.
Un bon compilateur d'optimisation devrait prendre
et compilez-le dans un noop. C'est certainement plus rapide que n'importe quelle alternative. Il a l'inconvénient de ne rien faire, mais tel est l'inconvénient d'un comportement indéfini.
la source
-Wall -Wextra
.Pas encore mentionné, mais les chemins de code qui invoquent un comportement non défini sont autorisés à faire tout ce que le compilateur veut, par exemple
Ce qui est certainement plus rapide que votre boucle correcte, et à cause de l'UB, est parfaitement conforme.
la source
Pour des raisons de sécurité, la nouvelle mémoire affectée à un programme doit être nettoyée, sinon les informations pourraient être utilisées et les mots de passe pourraient fuir d'une application à une autre. Ce n'est que lorsque vous réutilisez la mémoire que vous obtenez des valeurs différentes de 0. Et il est très probable que sur une pile la valeur précédente soit juste fixe, car l'utilisation précédente de cette mémoire est fixe.
la source
Votre exemple de code particulier ne ferait probablement pas ce que vous attendez. Alors que techniquement chaque itération de la boucle recrée les variables locales pour les valeurs r, g et b, en pratique c'est exactement le même espace mémoire sur la pile. Par conséquent, il ne sera pas re-randomisé à chaque itération, et vous finirez par attribuer les mêmes 3 valeurs pour chacune des 1000 couleurs, quelle que soit la façon dont les r, g et b sont aléatoires individuellement et initialement.
En effet, si cela fonctionnait, je serais très curieux de savoir ce qui le re-randomise. La seule chose à laquelle je peux penser serait une interruption entrelacée qui empaqueterait au sommet de cette pile, très peu probable. Peut-être qu'une optimisation interne qui les garderait comme variables de registre plutôt que comme de véritables emplacements de mémoire, où les registres sont réutilisés plus bas dans la boucle, ferait aussi l'affaire, surtout si la fonction de visibilité définie est particulièrement gourmande en registres. Pourtant, loin d'être aléatoire.
la source
Comme la plupart des gens ici ont mentionné un comportement indéfini. Undefined signifie également que vous pouvez obtenir une valeur entière valide (heureusement) et dans ce cas, ce sera plus rapide (car l'appel de fonction rand n'est pas effectué). Mais ne l'utilisez pas pratiquement. Je suis sûr que ce sera terrible, car la chance n'est pas toujours avec vous.
la source
Vraiment mauvais! Mauvaise habitude, mauvais résultat. Considérer:
Si la fonction
A_Function_that_use_a_lot_the_Stack()
fait toujours la même initialisation, elle laisse la pile avec les mêmes données dessus. Ces données sont ce que nous appelonsupdateEffect()
: toujours la même valeur! .la source
J'ai effectué un test très simple, et ce n'était pas du tout aléatoire.
Chaque fois que j'ai exécuté le programme, il imprimait le même numéro (
32767
dans mon cas) - vous ne pouvez pas obtenir beaucoup moins aléatoire que cela. Il s'agit probablement du code de démarrage de la bibliothèque d'exécution laissé sur la pile. Puisqu'il utilise le même code de démarrage à chaque exécution du programme et que rien d'autre ne varie dans le programme entre les exécutions, les résultats sont parfaitement cohérents.la source
Vous devez avoir une définition de ce que vous entendez par «aléatoire». Une définition sensée implique que les valeurs que vous obtenez doivent avoir peu de corrélation. C'est quelque chose que vous pouvez mesurer. Il n'est pas non plus trivial de réaliser de manière contrôlée et reproductible. Un comportement indéfini n'est donc certainement pas ce que vous recherchez.
la source
Il existe certaines situations dans lesquelles la mémoire non initialisée peut être lue en toute sécurité en utilisant le type "unsigned char *" [par exemple un tampon renvoyé par
malloc
]. Le code peut lire une telle mémoire sans avoir à se soucier du fait que le compilateur jette la causalité par la fenêtre, et il y a des moments où il peut être plus efficace de préparer du code pour tout ce que la mémoire peut contenir que de s'assurer que les données non initialisées ne seront pas lues ( un exemple courant de cela serait d'utilisermemcpy
sur un tampon partiellement initialisé plutôt que de copier discrètement tous les éléments qui contiennent des données significatives).Même dans de tels cas, cependant, il faut toujours supposer que si une combinaison d'octets est particulièrement vexatoire, sa lecture donnera toujours ce modèle d'octets (et si un certain modèle serait vexatoire en production, mais pas en développement, un tel le modèle n'apparaîtra pas tant que le code n'est pas en production).
La lecture de la mémoire non initialisée peut être utile dans le cadre d'une stratégie de génération aléatoire dans un système embarqué où l'on peut être sûr que la mémoire n'a jamais été écrite avec un contenu sensiblement non aléatoire depuis la dernière mise sous tension du système, et si la fabrication processus utilisé pour la mémoire fait varier son état de mise sous tension de façon semi-aléatoire. Le code devrait fonctionner même si tous les appareils fournissent toujours les mêmes données, mais dans les cas où, par exemple, un groupe de nœuds doit chacun sélectionner des ID uniques arbitraires le plus rapidement possible, ayant un générateur "peu aléatoire" qui donne à la moitié des nœuds la même initiale L'ID pourrait être mieux que de ne pas avoir de source initiale de hasard.
la source
Comme d'autres l'ont dit, ce sera rapide, mais pas aléatoire.
Ce que la plupart des compilateurs feront pour les variables locales, c'est de prendre de l'espace pour eux sur la pile, mais pas la peine de le définir sur quoi que ce soit (la norme dit qu'ils n'en ont pas besoin, alors pourquoi ralentir le code que vous générez?).
Dans ce cas, la valeur que vous obtiendrez dépendra de ce qui était précédemment sur la pile - si vous appelez une fonction avant celle-ci qui a une centaine de variables de caractères locales toutes définies sur 'Q' puis appelez votre fonction après qui revient, alors vous trouverez probablement que vos valeurs «aléatoires» se comportent comme si vous les aviez
memset()
toutes à «Q».Surtout pour votre exemple de fonction essayant d'utiliser ceci, ces valeurs ne changeront pas chaque fois que vous les lirez, elles seront les mêmes à chaque fois. Ainsi, vous obtiendrez 100 étoiles toutes définies sur la même couleur et la même visibilité.
De plus, rien ne dit que le compilateur ne devrait pas initialiser ces valeurs - donc un futur compilateur pourrait le faire.
En général: mauvaise idée, ne le faites pas. (comme beaucoup d'optimisations de niveau de code "intelligentes" vraiment ...)
la source
Comme d'autres l'ont déjà mentionné, il s'agit d'un comportement indéfini ( UB ), mais cela peut "fonctionner".
À l'exception des problèmes déjà mentionnés par d'autres, je vois un autre problème (inconvénient) - il ne fonctionnera pas dans un langage autre que C et C ++. Je sais que cette question concerne le C ++, mais si vous pouvez écrire du code qui sera un bon code C ++ et Java et que ce n'est pas un problème, alors pourquoi pas? Peut-être qu'un jour, quelqu'un devra le porter dans une autre langue et rechercher des bugs causés par
"tours de magie"UB comme celui-ci sera certainement un cauchemar (en particulier pour un développeur C / C ++ inexpérimenté).Ici, il est question d'un autre UB similaire. Imaginez-vous essayer de trouver un bug comme celui-ci sans connaître cet UB. Si vous voulez en savoir plus sur ces choses étranges en C / C ++, lisez les réponses aux questions du lien et voyez ce GRAND diaporama. Cela vous aidera à comprendre ce qui se cache sous le capot et comment cela fonctionne; ce n'est pas seulement un autre diaporama plein de "magie". Je suis sûr que même la plupart des programmeurs C / c ++ expérimentés peuvent en apprendre beaucoup.
la source
Ce n'est pas une bonne idée de s'appuyer sur une logique quelconque sur un comportement indéfini du langage. En plus de tout ce qui est mentionné / discuté dans cet article, je voudrais mentionner qu'avec l'approche / le style C ++ moderne, ce programme peut ne pas être compilé.
Cela a été mentionné dans mon post précédent qui contient l'avantage de la fonction automatique et un lien utile pour la même chose.
https://stackoverflow.com/a/26170069/2724703
Donc, si nous modifions le code ci-dessus et remplaçons les types réels par auto , le programme ne compilera même pas.
la source
J'aime ta façon de penser. Vraiment hors des sentiers battus. Cependant, le compromis n'en vaut vraiment pas la peine. Le compromis mémoire-exécution est une chose, y compris un comportement non défini pour l'exécution n'est pas .
Cela doit vous donner un sentiment très troublant de savoir que vous utilisez un tel "aléatoire" que votre logique métier. Je ne le ferais pas.
la source
Utilisez
7757
chaque endroit où vous êtes tenté d'utiliser des variables non initialisées. Je l'ai choisi au hasard dans une liste de nombres premiers:c'est un comportement défini
il est garanti de ne pas toujours être 0
c'est premier
il est susceptible d'être aussi aléatoire statistiquement que les variables non initialisées
il est susceptible d'être plus rapide que les variables non initialisées car sa valeur est connue au moment de la compilation
la source
Il y a encore une possibilité à considérer.
Les compilateurs modernes (ahem g ++) sont si intelligents qu'ils parcourent votre code pour voir quelles instructions affectent l'état, et ce qui ne le fait pas, et si une instruction est garantie de ne PAS affecter l'état, g ++ supprimera simplement cette instruction.
Voici donc ce qui va se passer. g ++ verra certainement que vous lisez, effectuez de l'arithmétique, enregistrez, ce qui est essentiellement une valeur de déchets, ce qui produit plus de déchets. Puisqu'il n'y a aucune garantie que la nouvelle poubelle soit plus utile que l'ancienne, elle supprimera simplement votre boucle. BLOOP!
Cette méthode est utile, mais voici ce que je ferais. Combinez UB (Undefined Behavior) avec la vitesse rand ().
Bien sûr, réduisez les
rand()
s exécutés, mais mélangez-les afin que le compilateur ne fasse rien que vous ne vouliez pas.Et je ne te virerai pas.
la source
L'utilisation de données non initialisées pour l'aléatoire n'est pas nécessairement une mauvaise chose si elle est effectuée correctement. En fait, OpenSSL fait exactement cela pour amorcer son PRNG.
Apparemment, cette utilisation n'était cependant pas bien documentée, car quelqu'un a remarqué que Valgrind se plaignait d'utiliser des données non initialisées et les "corrigeait", provoquant un bogue dans le PRNG .
Vous pouvez donc le faire, mais vous devez savoir ce que vous faites et vous assurer que toute personne lisant votre code comprend cela.
la source