54 votes

En C99, f () + g () est-il indéfini ou simplement non spécifié?

J'ai l'habitude de penser qu'en C99, même si les effets secondaires des fonctions f et g interféré, et bien que l'expression f() + g() ne contient pas de point de séquence, f et g contiendrait certains, de sorte que le comportement est indéterminé: soit f() sera appelée avant g(), ou g() avant de f().

Je n'en suis plus si sûr. Que faire si le compilateur inlines les fonctions (dont le compilateur peut décider de le faire, même si les fonctions ne sont pas déclarées inline) puis réorganise les instructions? Peut-on obtenir un résultat différent des deux ci-dessus? En d'autres termes, est-ce un comportement indéfini?

Ce n'est pas parce que j'ai l'intention d'écrire ce genre de chose, c'est de choisir la meilleure étiquette pour une telle déclaration dans un analyseur statique.

25voto

Jonathan Leffler Points 299946

L'expression f() + g() contient un minimum de 4 séquence de points; l'un avant l'appel à l' f() (après tout zéro de ses arguments sont évalués); l'un avant l'appel à l' g() (après tout zéro de ses arguments sont évalués); l'un comme l'appel à l' f() retours; et l'un comme l'appel à l' g() les rendements. De plus, les deux séquence de points associés aux f() se soit avant ou après la séquence de deux points associés avec g(). Ce que vous ne pouvez pas dire qui est de l'ordre de la séquence de points va se produire, si les f-points se produire avant que le g-points ou vice versa.

Même si le compilateur inline le code, il doit obéir à la "comme si" la règle - le code doit comporter les mêmes que si les fonctions n'ont pas été entrelacés. Qui limite la portée des dommages (en supposant un non-buggy compilateur).

Donc, dans l'ordre d' f() et g() sont évaluées n'est pas spécifié. Mais tout le reste est assez propre.


Dans un commentaire, supercat demande:

Je m'attends à des appels de fonction dans le code source de rester comme une séquence de points, même si un compilateur décide de sa propre associé. Le fait reste vrai pour les fonctions déclarées "inline", ou ne le compilateur d'obtenir plus de latitude?

Je crois que le "comme si" la règle s'applique et que le compilateur n'est pas d'obtenir plus de latitude pour omettre la séquence de points car il utilise explicitement inline fonction. La principale raison de penser que les (trop paresseux pour chercher le libellé exact de la norme), c'est que le compilateur est permis de l'inclure ou de ne pas incorporer une fonction selon ses règles, mais le comportement du programme ne devrait pas changer (sauf pour la performance).

Aussi, ce qui peut être dit à propos de la séquence de (a(),b()) + (c(),d())? Est-il possible pour c() et/ou d() à exécuter entre a() et b(), ou pour a() ou b() à exécuter entre c() et d()?

  • Clairement, s'exécute avant que b et c s'exécute avant d. Je crois qu'il est possible pour le c et le d exécuté entre a et b, mais il est assez peu probable que le compilateur générera le code tel qu'il est; de même, a et b peuvent être exécutées entre c et d. Et bien que j'ai utilisé " et "dans" c et d", qui pourrait être un " ou " - c'est l'une de ces séquences de fonctionnement répondre aux contraintes:

    • abcd
    • acbd
    • acdb
    • cadb
    • cdab
    • cabd

    Je ne suis pas certain que de dresser une liste exhaustive, mais elle couvre la plupart des variantes.

Si une telle chose serait possible, ce qui impliquerait une différence significative entre les fonctions inline et les macros.

Il existe d'importantes différences entre les fonctions inline et les macros, mais je ne pense pas que la commande de l'expression est l'un d'entre eux. C'est, l'une des fonctions a, b, c ou d, pourrait être remplacé par une macro, et même le séquençage de la macro organismes pourraient se produire. La principale différence, il me semble, c'est qu'avec les fonctions inline, il y a la garantie de la séquence des points lors des appels de fonction, comme il est précisé dans la principale réponse à la virgule opérateurs. Avec des macros, vous perdez la fonction de séquence de points. (Donc, peut-être que la différence est importante...) Cependant, dans de nombreuses façons le problème est plutôt comme des questions au sujet de combien d'anges peuvent danser sur la tête d'une épingle - il n'est pas très important dans la pratique. Si quelqu'un m'a présenté avec l'expression (a(),b()) + (c(),d()) dans une revue de code, je voudrais leur dire de réécrire le code pour le rendre clair:

a();
c();
x = b() + d();

Et qui suppose, il n'y a pas de critique de séquençage de l'exigence sur b() vs d().

14voto

R.. Points 93718

Voir l’annexe C pour une liste des points de séquence. Les appels de fonction (le point entre tous les arguments en cours d’évaluation et d’exécution passant à la fonction) sont des points de séquence. Comme vous l'avez dit, la fonction appelée en premier n'est pas spécifiée, mais chacune des deux fonctions verra tous les effets secondaires de l'autre, ou aucun.

1voto

Pascal Cuoq Points 39606

@dmckee

Eh bien, qui ne correspondent pas à l'intérieur d'un commentaire, mais voici la chose:

Tout d'abord, vous écrivez un bon analyseur statique. "Correct", dans ce contexte, signifie qu'il ne va pas rester silencieux si il n'y a rien de douteux sur le code analysé, de sorte à ce stade, vous allègrement l'amalgame entre indéfini et indéterminé des comportements. Ils sont à la fois mauvais et inacceptable dans le code critique, et de vous mettre en garde, à juste titre, pour deux d'entre eux.

Mais vous ne voulez avertir une fois pour un éventuel bug, et aussi vous savez que votre analyseur sera jugé dans les tests de performances en termes de "précision" et "rappel" par rapport à d'autres, peut-être pas correct, analyseurs, donc il ne faut pas avertir deux fois sur un même problème... que ce Soit un vrai ou fausse alarme (vous ne savez pas qui. vous ne savez jamais qui, sinon ce serait trop facile).

Si vous souhaitez émettre un seul avertissement pour

*p = x;
y = *p;

Parce que dès qu' p est un pointeur valide lors de la première déclaration, il peut être supposé pour être un pointeur valide lors de la deuxième déclaration. Et pas de déduire cette mesure permettra de réduire votre score sur la précision métrique.

Si vous enseignez à votre analyseur de supposer qu' p est un pointeur valide dès que vous ont mis en garde à ce sujet la première fois dans le code ci-dessus, de sorte que vous n'avez pas prévenu la deuxième fois. Plus généralement, vous apprendre à ignorer les valeurs (et de chemins d'exécution) qui correspondent à quelque chose que vous avez déjà averti.

Ensuite, vous vous rendez compte que pas beaucoup de gens sont de l'écriture de code critique, afin de vous faire d'autres, léger analyses pour le reste d'entre eux, sur la base des résultats de la première analyse correcte. Dire, un programme C de la trancheuse.

Et vous vous dites "eux": Vous n'avez pas à vérifier toutes les (fausses) alarmes émises par la première analyse. Les tranches de programme se comporte de la même que le programme d'origine aussi longtemps qu'aucun d'eux n'est déclenchée. La machine produit des programmes qui sont l'équivalent pour le découpage de critère pour "défini" les chemins d'exécution.

Et les utilisateurs gaiement ignorer les alarmes et l'utilisation de la trancheuse.

Et puis vous vous rendez compte que peut-être il y a un malentendu. Par exemple, la plupart des implémentations de memmove (vous savez, celui qui gère les blocs qui se chevauchent) invoquer un comportement non spécifié lorsqu'il est appelé, avec des liens qui ne pointent pas sur le même bloc (en comparant les adresses qui ne pointent pas sur le même bloc). Et votre analyseur d'ignorer les deux chemins d'exécution, parce que les deux ne sont pas spécifiés, mais dans la réalité les deux chemins d'exécution sont équivalents et tout est bien.

Donc il ne devrait pas y avoir de malentendu sur la signification des alarmes, et si l'on a l'intention de les ignorer, seulement incomparable des comportements indéfinis, devraient être exclues.

Et c'est ainsi que vous vous retrouvez avec un fort intérêt dans la distinction entre un comportement non spécifié et un comportement indéfini. Personne ne peut vous blâmer pour en ignorant cette dernière. Mais les programmeurs vont écrire l'ancienne, sans même y penser, et quand vous dites que votre trancheuse exclut les "mauvais comportements" du programme, ils ne vont pas se sentir comme ils sont concernés.

Et c'est la fin d'une histoire qui n'a certainement pas de place dans un commentaire. Mes excuses à tous ceux qui ont lu jusque-là.

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