12
votes

C - est une valeur indéterminée indéterminable?

Selon ce Post Une valeur indéterminée est la suivante: xxx

Selon Google, la définition d'indéterminée est la suivante:

  • pas certain, connu ou établi
  • laissé douteux; vague.

    Selon la zone décisive, déterminable est:

    • capable d'être déterminé

      Selon Merriam-Webster, pour déterminer (dans le contexte particulier) est:

      • pour savoir ou venir à une décision concernant l'enquête, le raisonnement ou le calcul

        Donc, le bon sens dit que même si une valeur indéterminée est inconnue pendant la compilation, il est parfaitement déterminable pendant l'exécution, par ex. Vous pouvez toujours lire ce qui arrive à occuper cet emplacement de mémoire.

        ou je me trompe? Si oui, pourquoi?

        Edit: Pour clarifier, je poste ceci par rapport à ce qui est devenu un argument chauffé avec un utilisateur qui a tenté de me convaincre qu'une valeur indéterminée est indéterminable, ce que j'aime beaucoup. < / P>

        Edit 2: Pour clarifier, par "déterminable", je ne veux pas dire une valeur stable ou utilisable, même s'il s'agit d'une valeur détruite pour une mémoire ininitialisée, la valeur de cette poubelle peut toujours être déterminée. Je veux dire que tenter de déterminer que la valeur donnera toujours une valeur plutôt que ... pas d'action. Donc, cette valeur doit provenir de la mémoire, allouée sous forme de stockage pour la valeur toujours indéterminée, je doute fort qu'un compilateur utilisera réellement un générateur de nombres aléatoires juste pour le bien de venir avec une valeur arbitraire. >


1 commentaires

L'a été un rapport de défaut qui a été passé plusieurs mois après votre question et a récemment eu une solution proposée qui couvre directement ce sujet et indique essentiellement que les valeurs indéterminées ne sont pas stables, elles peuvent modifier des valeurs de l'évaluation à l'évaluation. Voir ma réponse pour plus de détails.


6 Réponses :


17
votes

Le fait qu'il soit indéterminé non seulement signifie qu'il est imprévisible à la première lecture, cela signifie également qu'il n'est pas garanti d'être stable. Cela signifie que la lecture de la même variable non initialisée n'est pas garantie de la même valeur. Pour cette raison, vous ne pouvez pas vraiment "déterminer" cette valeur en le lisant. (Voir DR # 260 pour l'initiale Discussion sur le sujet de 2004 et DR # 451 réaffirmant que la position en 2014.)

Par exemple, une variable A peut être affectée à un registre CPU R1 avec un certain délai (place de la mémoire ). Afin d'établir le calendrier optimal de mission variables à enregistrer, le concept de langage de "objet de vie" n'est pas suffisamment précis. Les registres de la CPU sont gérés par un compilateur d'optimisation basé sur un concept beaucoup plus précis de "valeur de vie". Valeur Lifetime commence lorsqu'une variable est attribuée à une valeur déterminée . Valeur Lifetime se termine lorsque la valeur déterminée précédemment attribuée est lue pour la dernière fois. Valeur Lifetime détermine le calendrier au cours de laquelle une variable est associée à un registre CPU spécifique. En dehors de ce calendrier attribué, le même registre R1 peut être associé à une variable complètement différente B . Essayer de lire une variable non initialisée A en dehors de sa valeur de vie de la valeur peut réellement entraîner une variable de lecture B , qui pourrait changer activement.

dans cet échantillon de code xxx

Le compilateur peut facilement déterminer que même la durée de vie d'objet de i et j chevauchement, la valeur de vie ne pas Le chevauchement du tout, ce qui signifie que les deux i et j peuvent être affectés au même registre de la CPU. Si quelque chose comme ça se passe, vous pourriez facilement découvrir que le premier cycle imprime la valeur constante de i sur chaque itération. Ceci est parfaitement compatible avec l'idée de valeur de j étant indéterminée.

Notez que cette optimisation ne nécessite pas nécessairement des registres de la CPU. Pour un autre exemple, un compilateur d'optimisation intelligent concerné par la préservation de l'espace de pile précieux pourrait analyser la valeur de la valeur dans l'échantillon de code ci-dessus et la transformer en xxx

avec des variables i < / code> et j occupant le même emplacement en mémoire à des moments différents. Dans ce cas, le premier cycle pourrait à nouveau finir par imprimer la valeur de i sur chaque itération.


10 commentaires

Vais-je obtenir une valeur arbitraire d'une variable d'enregistrement à partir d'un autre, ou autre chose?


@DDRiver: Je ne suis pas sûr de ce que vous entendez par "enregistrement variable" ici. Toute variable peut être stockée dans un registre sans votre connaissance, ce qui signifie que vous pouvez observer le même comportement de toute variable. De plus, la même technique peut être utilisée théoriquement la même technique pour enregistrer la mémoire de pile. L'effet peut donc être observé même sans optimisations liées au registre.


Je veux dire une valeur qui ne sort jamais du registre et de la mémoire cache / de la RAM, je soupçonne que les registres contiennent des déchets de restes tout comme une autre mémoire. Il aura donc toujours une représentation binaire valide.


@DDRiver: Oui, mais ce n'est pas le point. Le point est que la relation étroite entre variable et registre ne commence pas tant qu'une valeur déterminée est écrit dans la variable. lecture une variable ne commence pas cette relation. Ainsi, jusqu'à ce moment, le registre est libre de changer autant qu'il souhaite pour des raisons externes, ce qui signifie que chaque lecture pourrait facilement produire une valeur différente.


Par curiosité, j'ai testé votre exemple de code: avec gcc IT imprime tout j égal à 0 , mais clang Imprime 2 ou 3 valeurs différentes, même dans -O0 .


@RODRIGO: GCC remplace littéralement j avec 0 dans le premier printf . Un exemple plus compliqué est nécessaire pour tromper GCC dans ce comportement, si c'est possible.


Pour moi, GCC 4.8 produit des résultats cohérents. Tous les J S sont 2686856. MSVC2012 ne compilera même pas ...


Les choses sont pires que ce que vous décrivez. Le code i = * p; j = i; faire des choses(); k = i; pourrait être remplacé par j = * p; faire des choses(); k = * p; si le compilateur pourrait déterminer que dostuff n'a pas pu altérer * p * dans tous les cas où il aurait une valeur déterminée. Par conséquent, même si les J et K étaient chargés de moi, et rien n'aurait pu modifier I, il n'y aurait aucune garantie J et K serait la même.


Cette réponse ne cite rien de faisant autorité ou de commentaires du Comité C. Quelle raison y a-t-il de croire?


@ERIC PostPischil: Le pouvoir impressionnant de l'autorité de l'auteur qui l'emporte sur tout comité puant :) En attendant, j'ai commandé certains de mes sous-vêtements pour ajouter des références à la réponse du DR N ° 260 et du Dr # 451 à la réponse.



11
votes

Deux lectures successives d'une valeur indéterminée peuvent donner deux valeurs différentes. En outre, la lecture d'une valeur indéterminée invoque un comportement non défini en cas de représentation d'un piège.

dans un Dr # 260 , C Comité a écrit:

Une valeur indéterminée peut être représentée par n'importe quel motif de bits. La norme C énonce sans condition que deux inspections des bits représentant une valeur donnée observent le même motif bit que le motif observé à chaque occasion sera une représentation valide de la valeur.

[...] pour atteindre notre réponse, nous avons noté que la nécessité de demander des modèles de bits immuables pour les valeurs indéterminées réduirait les opportunités d'optimisation. Par exemple, il nécessiterait un suivi des motifs de bits réels de valeurs indéterminées si la mémoire contenant des éléments a été poncée. Cela semble une contrainte inutile sur les optimiseurs sans avantage compensatoire aux programmeurs.


16 commentaires

Il n'est pas nécessaire que la valeur ne soit persistante, uniquement pour l'obtenir, évidemment une valeur indéterminée est inutile est un contexte pratique. Mon point était que même si le programmeur la laissait indéterminé la valeur est toujours déterminée techniquement par une représentation binaire, quel que soit son être.


@DDRiver Je comprends ce que vous dites et je pense que les 3 meilleurs interrogateurs le comprennent également. Mais la chose est, si vous lisez une variable ininitialisée, le compilateur peut même ne pas vous soucier de vous apporter une représentation binaire (ordures). Voir le lien de Pascal, il s'adresse et explique exactement ce que vous demandez.


@Theodoroschatzigiannakis - Je comprends cela, mais est-ce standard ou sa mise en œuvre?


@DDRiver C11 indique que la lecture d'un objet automatique ininitialisé invoque un comportement indéfini 6.3.2.1P2) "Si la LVALUE désigne un objet de durée de stockage automatique qui aurait pu être déclarée avec la classe de stockage de registres (jamais eu son adresse prise), et cet objet est ininitialisé (non déclaré avec un initialiseur et aucune mission n'a été effectué avant utilisation), le comportement est indéfini. " voir le Dr # 338 pour plus d'informations Open-Std.org/jtc1/sc22/wg14/www/docs/dr_338.htm


@DDRiver Le fait que la valeur soit indéterminée est standard. Le fait que la lecture soit invoque un comportement indéfini est la norme. Ce qui arrive réellement, c'est une mise en œuvre spécifique.


@DDRiver "Lecture informatique ud" signifie que lorsque le compilateur détecte l'accès à une telle mémoire, il peut vous permet de lire une représentation binaire à déchets, mais il peut aussi bien enlever l'expression complètement (probable), Cela peut le relier à un RNG, cela peut le remplacer par un code pour cracer le processus ou invoquer des démons d'une dimension d'enfer, tout sans violer la norme. Certaines de ces approches potentielles (comme le crash du processus) ne cèdent aucun type de valeur du tout, la valeur peut donc être vraiment non déterminable . N'oubliez pas que le compilateur traite d'abord en sémantique.


Donc, dans l'ensemble, en fonction de la mise en œuvre et du contexte d'utilisation spécifique, je peux soit obtenir des déchets aléatoires à travers une représentation binaire ou une autre poubelle aléatoire qui n'est pas une représentation binaire ou si une chance supplémentaire, un crash?


@DDRiver Strictement parlant, non, je ne dirais pas "non plus /", comme c'était simplement quelques exemples de ce qui pourrait arriver - mais la liste n'est définitivement pas exhaustive. Dans l'ensemble, je dirais que invoquer un comportement indéfini signifie pratiquement que n'importe quoi peut arriver, en fonction de n'importe quel nombre de facteurs .


@Theodoroschatzigiannakis - mais pourquoi? Le scénario peut être très facilement réduit dans un comportement bien défini, quel est l'avantage d'avoir «tout peut arriver» par rapport à un comportement défini et sûr? En outre, je n'utiliserais pas "quoi que ce soit" puisque je peux penser à beaucoup de choses qui ne pouvaient pas se produire dans ce scénario, pas dans une quadrillion d'années ... je demande parce que je me couche moi-même (jusqu'à présent un compilateur très naïf) et décidé de faire exactement cela, vient d'aller chercher la valeur de la mémoire, quoi que ce soit. Si la valeur est référencée, son emplacement n'est pas réutilisé pour d'autres valeurs.


@DDRiver: Le comportement indéfini n'est qu'une prise pour "qui donne une merde? Ne fais tout simplement pas ça". Alors: Qui donne une merde? Juste ne fais pas ça. C'est tout ce que cela signifie, vous ne trouverez plus de sens. La raison de tout ce que vous voulez, en ce qui concerne la langue: ne le faites pas.


C'est une extrémité lâche, c'est ce que c'est ce que c'est, et des fins desserrées ... ne sont jamais bonnes dans mon livre. Je vais donc le garder gentil et attaché dans ma mise en œuvre.


@DDRiver Souvent, des choses sont souvent indéfinies afin de donner au compilateur de l'espace pour effectuer des performances et des optimisations de mémoire. (Il peut y avoir d'autres avantages aussi que je ne peux pas penser à l'heure actuelle.) Quant à pourquoi, il n'y a pas vraiment de réponse définitive, autrement que les concepteurs de la langue C valorisent ces avantages suffisamment pour permettre ce compromis. D'autres langues ont moins de UD parce que leurs concepteurs ont beaucoup plus de prévisibilité. Ce n'est pas une erreur des designers de C (ni une surveillance ou une fin lâche), c'est simplement une décision de conception linguistique.


@Theodoroschatzigiannakis: Les auteurs de la norme pensaient que les personnes produisant des implémentations devraient être mieux en mesure de juger des besoins de leurs clients que les personnes du comité. Malheureusement, les écrivains compilateurs ont interprété la permission de faire preuve de jugement comme si les auteurs du Comité avaient déterminé que les utilisateurs du compilateur n'ont presque pas besoin d'avoir besoin de respect.


Le Dr indiqué dans cette réponse indique que les bits représentant une valeur peuvent être différents dans différentes observations, et non que la valeur peut changer. E.G., les bits de rembourrage peuvent être différents. Il est même dit que les bits constitueront une représentation valide de la valeur , pas A .


@ERICPOSTPISCHIL: Point intéressant, mais les interprétations modernes de la norme vont au-delà de cela. Si toutes les tentatives de lecture d'une valeur indéterminée d'un type sans bits de rembourrage ou des représentations de piège ont été garanties pour générer une valeur arbitraire de ce type, mais les lectures répétées n'étaient pas nécessaires pour être cohérentes, le code nécessitant une cohérence pourrait lire une valeur et l'écrire. Malheureusement, de nombreux optimiseurs supprimeront le code comme x = x; entièrement plutôt que de le remplacer par un intrinsèque qui signifierait "si x est indéterminé, le rendre non spécifié; sinon ne rien faire", même ...


... Lorsqu'un optimiseur ultérieur n'atteinta pas une telle sémantique autrement, et les écrivains du compilateur semblent avoir convaincu le comité de fixer de telles "optimisations" cassées nécessiteraient une quantité de travail impraticable.



5
votes

3 commentaires

Je l'affiche dans un contexte limité, la valeur ne doit être déterminée que dans une situation qu'il peut potentiellement échouer, comme la division par une valeur indéterminée.


@DDRiver dans non signé int j; ... J * = 2; C'est l'utilisation d'une valeur indéterminée, pas la multiplication par deux, qui est un comportement indéfini et provoque le résultat d'une multiplication par deux d'être impair. La multiplication non signée est toujours définie.


L'article lié contient une excellente clarification de diverses idées fausses que nous avons souvent à propos des locaux non initialisés. +1.



2
votes

Nous ne pouvons pas déterminer la valeur d'une valeur indéterminée, même sous des opérations qui conduisaient normalement à des valeurs prévisibles telles que la multiplication par zéro. La valeur est wobbly em> selon la nouvelle langue proposée (voir modification).

Nous pouvons trouver les détails pour cela dans rapport de défaut # 451: l'instabilité des variables automatiques non initialisées qui avait une résolution proposée sur une année après cette question. P>

Ce rapport de défaut couvre un terrain très similaire à votre question. Trois questions ont été adressées: p>

  1. peut une variable non initialisée avec une durée de stockage automatique (d'un type qui ne dispose pas de valeurs de piégeage, dont l'adresse a été prise ainsi de 6.3.2.1P2 ne s'applique pas et qui n'est pas volatile) modifie sa valeur sans action directe de la programme? Li>
  2. Si la réponse à la question 1 est "oui", alors quelle distance peut-être ce type d'instabilité se propager? li>
  3. Si des valeurs "instables" peuvent se propager par des arguments de fonction dans une fonction appelée, peut appeler une fonction de bibliothèque standard C présentant un comportement non défini à cause de cela? li> ol>

    et fourni les exemples suivants avec d'autres questions: P>

    x[0] = x[0];
    x[0] += 0;
    x[0] *= 0;
    
    • La réponse à la question 1 est "oui", une valeur non initialisée dans les conditions décrites peut sembler changer sa valeur. LI>
    • La réponse à la question 2 est que toute opération effectuée sur des valeurs indéterminées aura une valeur indéterminée à la suite. LI>
    • La réponse à la question 3 est que les fonctions de la bibliothèque présenteront un comportement indéfini lorsqu'ils sont utilisés sur des valeurs indéterminées. LI>
    • Ces réponses sont appropriées pour tous les types qui n'ont pas de représentations de pièges. LI>
    • Ce point de vue réaffirme la position C99 DR260. LI>
    • Le comité convient que cette zone bénéficierait d'une nouvelle définition de quelque chose qui s'apparente à une valeur «vacillante» et que cela devrait être pris en compte dans une révision ultérieure de cette norme. Li>
    • Le comité note également que le rembourrage des octets dans les structures est peut-être une forme distincte de représentation "vacillante". li> ul>

      mise à jour vers l'adresse EDIT B> P>

      Une partie de la discussion comprend ce commentaire: P>

      • Strong Sentiment Formé, en partie basé sur une expérience antérieure dans le développement de l'annexe L, une nouvelle catégorie de valeur "wobbly" est nécessaire. Le problème sous-jacent est que les compilateurs modernes de la propagation de la valeur de la piste et des valeurs non incialisées synthétisées pour une utilisation initiale d'un objet peuvent être supprimées comme sans conséquence avant de synthétiser une valeur différente pour une utilisation ultérieure. Exiger autrement défaite les optimisations importantes du compilateur. Toutes les utilisations de valeurs «vacillantes» peuvent être considérées comme un comportement indéfini. li> ul> blockQuote>

        Vous pourrez donc déterminer une valeur mais la valeur pourrait changer à chaque évaluation. p> p>


3 commentaires

Donc, si vous pouvez déterminer la valeur, que même une valeur déchette instable, cette valeur doit venir de quelque part? Par exemple. Le compilateur a attribué un peu de stockage pour cela et vous obtenez une valeur de cette adresse, même si la variable est indéterminée, vous pouvez obtenir une valeur hors de la représentation binaire de son emplacement de stockage. Je pense que logiquement, s'il est incroyablement de déterminer une valeur indéterminée, le compilateur n'autoriserait même pas au moins un accès direct, mais cela n'est pas fait car évidemment, vous pouvez le faire indirectement, alors même des valeurs indéterminées doivent venir de certains emplacements de stockage. .


@DDRiver: De nombreux types de programmes qui traitent des choses comme des tableaux de plaide et des tables de hachage peuvent bénéficier de garanties en vrac concernant les valeurs de stockage ininitialisé. Ce code peut, par exemple, être en mesure d'initialiser une structure de données de réseau de graouse de million d'entrée à l'aide de deux magasins plutôt qu'un million. Si une implémentation peut garantir que uint32_t x = (volatile uint32_t) (table-> arr [y]); au pire ensemble x sur un nombre arbitraire 0 à 4294967295 Si Tableau-> ARR [Y] est ininitialisé, même s'il n'offre aucune garantie qui se lit répétée sur le même élément non initialisé ...


... donnerait la même valeur, qui permettrait à Code d'enregistrer un million de magasins à ce qui devrait être un coût minimal aux optimisations utiles. Malheureusement, les écrivains compilateurs ont perdu de vue de ce qui devrait être un principe directeur d'optimisation: d'abord, ne faites pas de mal.



1
votes

Lorsque la norme introduit un terme comme indéterminé , il s'agit d'un terme normatif: la définition de la norme s'applique et non une définition de dictionnaire. Cela signifie qu'une valeur indéterminée n'est rien de plus ou moins qu'une valeur non spécifiée, ou une représentation de piège. Signification anglaise ordinaire de indéterminée ne sont pas applicables.

Même les termes non définis dans la norme peuvent être normatifs, via l'inclusion de références normatives. Par exemple, la section 2 de la norme C99 comprend normellement un document appelé ISO / CEI 2382-1: 1993, Technologies de l'information - Vocabulaire - Partie 1: Termes fondamentaux. . .

Cela signifie que si un terme est utilisé dans la norme, et n'est pas défini dans le texte (non introduit dans italique et expaind, et non donné dans la section Conditions), il pourrait néanmoins être une mot du document de vocabulaire ci-dessus; Dans ce cas, la définition de cette norme s'applique.


2 commentaires

Oui, la norme "ombres" la définition de "indéterminée", mais je ne suis pas au courant de la norme introduisant un nouveau sens pour "déterminer" ou "déterminable". La grande question imo est d'où provient la valeur d'une valeur indéterminée et mon hypothèse provient d'un stockage de pile automatique alloué. J'ai écrit un compilateur moi-même et j'alloque un stockage sur la première utilisation d'une variable, que ce soit celui-ci, lisez-le ou lisez son adresse, parce que je pense que c'est ce qui est logique. Mais beaucoup de gens semblent déduire que ce n'est peut-être pas le cas, alors je me demande si pas du stockage, alors d'où?


@DDriver "Valeur pour une valeur indéterminée" est sémantiquement incorrecte. Vous voulez probablement dire "quelle est la valeur d'un objet qui a une valeur indéterminée" et la réponse est "indéterminée".



-1
votes

Les auteurs de la norme ont reconnu qu'il existe des cas où il pourrait être coûteux pour une implémentation afin de garantir que le code qui lit une valeur indéterminée ne se comporte pas de manière incompatible avec la norme (par exemple, la valeur d'un uint16_t peut ne pas être dans la plage 0..65535 . Bien que de nombreuses implémentations puissent être à moindre coût de garanties comportementales utiles sur la manière dont les valeurs indéterminées comportent dans d'autres cas que la norme nécessite, Les plates-formes matérielles et les champs d'application signifient qu'aucun jeu de garanties unique ne serait optimal à toutes fins. Par conséquent, la norme permette simplement la matière comme une question de qualité de mise en œuvre.

La norme permettrait certainement une mise en œuvre de la qualité de la poubelle-la plus conforme de traiter presque toutes les utilisations de par exemple. Un uint16_t comme une invitation à libérer des démons nasaux. Il ne dit rien sur la question de savoir si des implémentations de haute qualité qui conviennent à diverses fins peuvent faire de même (et toujours être considérées comme des implémentations de haute qualité adaptées à ces fins). Si l'on doit accueillir des implémentations conçues pour piéger les éventuelles fuites de données inattendues, il peut être nécessaire de dégager explicitement des objets dans certains cas où leur valeur sera finalement ignorée, mais lorsque la mise en œuvre ne pouvait pas prouver qu'elle ne manquerait jamais d'informations. De même, si on doit accueillir des implémentations dont les "optimiseurs" sont conçus sur lesquelles des implémentations de mauvaise qualité-mais-conformité sont autorisées à faire, plutôt que quelles implémentations générales de haute qualité devraient faire, ces "optimiseurs" peuvent Effectuez nécessaire d'ajouter du code sinon inutile pour effacer les objets, même lorsque le code ne se soucie pas de la valeur (réduisant ainsi l'efficacité) afin d'éviter d'avoir les «optimiseurs» traiter le défaut de le faire comme une invitation à se comporter de manière absolument. < / p>


0 commentaires