32 votes

Quelle est la raison pour laquelle la sémantique de a = a ++ n'est pas définie?

a = a++;

est un comportement indéterminé dans C. La question que je pose est : pourquoi?

Je veux dire qu'il pourrait être difficile de définir l'ordre dans lequel les choses doivent être faites. Mais, certains compilateurs toujours le faire dans un ordre ou dans l'autre (à un niveau d'optimisation). Alors, pourquoi est-ce à gauche jusqu'au compilateur de décider?

Pour être clair, je veux savoir si c'était une décision de conception et, dans l'affirmative, ce qui a incité il? Ou peut-être il ya une limitation matérielle de quelque sorte?

(Note : Si la question du titre ne semble pas clair ou pas assez bon, les commentaires et/ou des changements sont les bienvenus)

47voto

Eric Lippert Points 300275

Mise à JOUR: Cette question a été l'objet de mon blog, le 18 juin 2012. Merci pour la grande question!


Pourquoi? Je veux savoir si c'était une décision de conception et, dans l'affirmative, ce qui a incité il?

Vous êtes à la demande, en substance, pour le procès-verbal de la réunion du C ANSI comité de conception, et je n'ai pas de ceux à portée de main. Si votre question ne peut être répondu définitivement par quelqu'un qui était dans la chambre, alors vous allez avoir à trouver quelqu'un qui était dans la salle.

Cependant, je peux répondre à une question plus large:

Quels sont certains des facteurs qui mènent une langue comité de conception de quitter le comportement d'un programme juridique (*) "undefined" ou "de mise en œuvre définies" (**)?

Le premier facteur majeur est: il y a deux implémentations existantes de la langue dans le marché qui sont en désaccord sur le comportement d'un programme particulier? Si FooCorp du compilateur compile M(A(), B()) "call A, B, appel M", et BarCorp du compilateur compile comme "appel en B, Un appel, appel M", et ni est la "évidemment correct" comportement puis il y a une forte incitation à la langue de conception de dire "vous êtes tous les deux de droite", et en faire la mise en œuvre de comportement défini. En particulier c'est le cas si FooCorp et BarCorp les deux ont des représentants sur le comité.

L'autre facteur important est: la fonctionnalité naturellement présente beaucoup de possibilités de mise en œuvre? Par exemple, en C# le compilateur de l'analyse d'une requête "compréhension" de l'expression est spécifié comme "ne syntaxique, la transformation en un programme équivalent qui n'ont pas d'interprétations de la requête, puis de les analyser qui est normalement le programme". Il y a très peu de liberté pour une mise en œuvre afin de faire autrement.

En revanche, le C# spécification indique que l' foreach boucle doivent être considérées comme équivalentes while boucle à l'intérieur d'un try bloc, mais permet la mise en œuvre d'une certaine souplesse. Un compilateur C# est permis de dire, par exemple "je sais comment le mettre en oeuvre foreach boucle de la sémantique de manière plus efficace sur un tableau" et utiliser le tableau d'indexation de l'entité plutôt que de la conversion de la matrice à une séquence que la spécification suggère qu'il devrait.

Un troisième facteur est: est la fonction si complexe qu'une ventilation détaillée de son comportement serait-il difficile ou cher de le préciser? La spécification C# est dit très peu d'effet sur la façon dont les méthodes anonymes, des expressions lambda, des arbres d'expression, dynamique des appels, iterator blocs et async blocs sont mises en œuvre; il se contente de décrire le désiré de la sémantique et de certaines restrictions sur le comportement, et laisse le reste jusqu'à la mise en œuvre.

Un quatrième facteur: la fonctionnalité d'imposer un lourd fardeau sur le compilateur à analyser? Par exemple, en C# si vous avez:

Func<int, int> f1 = (int x)=>x + 1;
Func<int, int> f2 = (int x)=>x + 1;
bool b = object.ReferenceEquals(f1, f2);

Supposons que nous avons besoin de b pour être vrai. Comment allez-vous déterminer si deux fonctions sont "les mêmes"? Faire un "intensionality" analyse -- les corps de fonction ont le même contenu? -- est dur, et de faire une "fusion" analyse -- les fonctions ont les mêmes résultats pour les mêmes entrées? -- est encore plus difficile. Un langage de spécification du comité devrait chercher à minimiser le nombre de problèmes de recherche que l'équipe de mise en oeuvre pour le résoudre!

En C# c'est donc parti pour être mise en œuvre définis; un compilateur peut choisir d'en faire référence égale ou non à sa discrétion.

Un cinquième facteur: la fonctionnalité d'imposer un lourd fardeau sur l'environnement d'exécution?

Par exemple, en C# déréférencement passé la fin d'un tableau est bien définie; elle produit un tableau d'index est en dehors des limites de l'exception. Cette fonction peut être mis en œuvre avec une petite -- pas de zéro, mais les petits-coût à l'exécution. L'appel d'une instance ou d'une méthode virtuelle avec une valeur null récepteur est définie comme la production d'un null-a-été déréférencé exception; encore une fois, cela peut être mis en œuvre avec une petite mais non nulle coût. L'avantage d'éliminer les comportements indéfinis paie pour les petites exécution de coût.

Un sixième facteur est: ne faire que le comportement défini exclure certains grands optimisation? Par exemple, C# définit l'ordre des effets secondaires lorsqu'on l'observe à partir du thread qui provoque des effets secondaires. Mais le comportement d'un programme qui observe les effets secondaires d'un thread à partir d'un autre thread est mise en œuvre définies à l'exception de quelques "spécial" des effets secondaires. (Comme une écriture volatile, ou en entrant une serrure.) Si le langage C# a exigé que tous les threads d'observer les mêmes effets secondaires dans le même ordre, alors nous aurions à restreindre les processeurs modernes de faire leur travail efficacement; les processeurs modernes dépendent de l'exécution et sophistiqués, les stratégies de mise en cache afin d'obtenir leur haut niveau de performance.

Ce ne sont que quelques facteurs qui viennent à l'esprit; il y a, bien sûr, beaucoup, beaucoup d'autres facteurs que la langue de la conception des comités de débat avant de prendre une fonction de "mise en œuvre définies" ou "indéfini".

Maintenant, nous allons revenir à votre exemple.

Le langage C# ne faire qu'un comportement strictement définies(); l'effet secondaire de l'augmentation est observée pour arriver avant les effets secondaires de la cession. Donc, il ne peut y avoir aucune "eh bien, c'est juste impossible" argument, parce qu'il est possible de choisir un comportement et de s'y tenir. Ni ce que cela empêche de grandes possibilités d'optimisations. Et il n'y a pas une multiplicité de possibles complexe la mise en œuvre des stratégies.

Ma supposition, donc, et j'insiste sur le fait que c'est une supposition, c'est que le langage C comité de commande sont les effets secondaires de la mise en œuvre du comportement défini parce qu'il y avait plusieurs compilateurs dans le marché qui fait les choses différemment, aucun n'a été clairement "plus correct", et le comité n'était pas disposé à dire la moitié d'entre eux qu'ils ont tort.


(*) Ou, parfois, son compilateur! Mais nous allons ignorer ce facteur.

(**) "Undefined" comportement signifie que le code peut faire quelque chose, y compris l'effacement du disque dur. Le compilateur n'est pas nécessaire pour générer le code qui a un comportement particulier, et n'est pas requise pour vous dire que c'est la génération de code avec un comportement indéterminé. "La mise en œuvre définies" comportement signifie que le compilateur auteur est donné une grande liberté dans le choix de la stratégie de mise en œuvre, mais il est nécessaire de choisir une stratégie, l'utiliser de manière cohérente, et le document de son choix.

() Lorsqu'on les observe à partir d'un seul fil, bien sûr.

11voto

hvd Points 42125

C'est undefined, car il n'y a aucune bonne raison pour écrire du code comme ça, et en ne nécessitant pas de tout comportement de faux code, les compilateurs peuvent plus agressive optimiser le bien-code écrit. Par exemple, *p = i++ peut être optimisé de manière à entraîner un plantage si p arrive à point d' i, peut-être parce que deux cœurs écrire sur le même emplacement de mémoire en même temps. Le fait que cela se passe aussi pour être indéfini dans le cas spécifique qu' *p est explicitement écrit que i, pour obtenir de l' i = i++, suit logiquement.

6voto

Andrew White Points 23508

C'est ambigu mais pas syntaxiquement faux. Que devraient être a ? Les = et ++ ont tous deux le même "timing". Ainsi, au lieu de définir un ordre arbitraire, il n'a pas été défini car l'un ou l'autre ordre serait en conflit avec l'une des deux définitions d'opérateurs.

6voto

John Bode Points 33046

À quelques exceptions près, l'ordre dans lequel les expressions sont évaluées est indéterminé; c'était une volonté délibérée de décision de conception, et il permet des implémentations pour réorganiser l'ordre d'évaluation de ce qui est écrit si cela résultera en un code machine plus efficace. De même, l'ordre dans lequel les effets secondaires de l' ++ et -- sont appliqués n'est pas spécifié au-delà de l'exigence qu'il arrive avant le prochain point de séquence, encore une fois à donner des implémentations de la liberté d'organiser des opérations de façon optimale.

Malheureusement, cela signifie que le résultat d'une expression comme a = a++ varient en fonction du compilateur, les paramètres du compilateur, dans les environs de code, etc. Le comportement est spécifiquement appelé comme indéfini dans la langue standard, de sorte que le compilateur réalisateurs n'avez pas à vous inquiéter au sujet de la détection de tels cas, et d'émettre un diagnostic contre eux. Des cas comme a = a++ sont évidents, mais que penser de quelque chose comme

void foo(int *a, int *b)
{
  *a = (*b)++;
}

Si c'est la seule fonction dans le fichier (ou si son interlocuteur est dans un autre fichier), il n'y a aucun moyen de savoir au moment de la compilation s' a et b pointent vers le même objet; que faites-vous?

Notez qu'il est tout à fait possible de mandat que toutes les expressions être évalué dans un ordre spécifique, et que tous les effets secondaires, être appliquée à un point spécifique dans l'évaluation; c'est ce que Java et C# n', et dans les langues des expressions comme a = a++ sont toujours bien définies.

3voto

Blagovest Buyukliev Points 22767

L'opérateur suffixe ++ renvoie la valeur avant l'incrémentation. Ainsi, à la première étape, a est affecté à son ancienne valeur (c'est ce que ++ renvoie). Au point suivant, il n'est pas défini si l'incrément ou l'affectation aura lieu en premier, car les deux opérations sont appliquées sur le même objet ( a ), et le langage ne dit rien sur l'ordre d'évaluation de ces opérateurs .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X