web-dev-qa-db-fra.com

L'instruction `int val = (++ i> ++ j)? ++ i: ++ j; `invoque un comportement indéfini?

Étant donné le programme suivant:

#include <stdio.h>
int main(void)
{
    int i = 1, j = 2;
    int val = (++i > ++j) ? ++i : ++j;
    printf("%d\n", val); // prints 4
    return 0;
}

L'initialisation de val semble pouvoir masquer un comportement indéfini, mais je ne vois aucun moment où un objet soit soit modifié plusieurs fois, soit modifié et utilisé sans point de séquence entre les deux. Quelqu'un pourrait-il me corriger ou corroborer à ce sujet?

24
max1000001

Le comportement de ce code est bien défini.

Il est garanti que la première expression d'un conditionnel sera évaluée avant la deuxième ou la troisième expression, et une seule de la deuxième ou de la troisième sera évaluée. Ceci est décrit dans la section 6.5.15p4 de la norme C :

Le premier opérande est évalué; il existe un point de séquence entre son évaluation et l'évaluation du deuxième ou du troisième opérande (celui qui est évalué). Le deuxième opérande n'est évalué que si le premier est différent de 0; le troisième opérande n'est évalué que si le premier est égal à 0; le résultat est la valeur du deuxième ou du troisième opérande (celui qui est évalué), converti dans le type décrit ci-dessous.

Dans le cas de votre expression:

int val = (++i > ++j) ? ++i : ++j;

++i > ++j est évalué en premier. Les valeurs incrémentées de i et j sont utilisées dans la comparaison, donc cela devient 2 > 3. Le résultat est faux, alors ++j est évalué et ++i n'est pas. Ainsi, la (encore) valeur incrémentée de j (c'est-à-dire 4) est ensuite affectée à val.

37
dbush

trop tard, mais peut-être utile.

(++i > ++j) ? ++i : ++j;

Dans le document ISO/IEC 9899:201xAnnex C(informative)Sequence points nous trouvons qu'il y a un point de séquence

Entre les évaluations du premier opérande du conditionnel?: Opérateur et celui des deuxième et troisième opérandes qui est évalué

Pour être bien défini, il ne faut pas modifier 2 fois (via des effets secondaires) le même objet entre 2 points de séquence.

Dans votre expression, le seul conflit qui pourrait apparaître serait entre le premier et le deuxième ++i Ou ++j.

À chaque point de séquence, la dernière valeur stockée dans l'objet doit correspondre à celle prescrite par la machine abstraite (c'est ce que vous calculeriez sur papier, comme sur une machine de Turing).

Citation de 5.1.2.3p3 Program execution

La présence d'un point de séquence entre l'évaluation des expressions A et B implique que chaque calcul de valeur et effet secondaire associé à A est séquencé avant chaque calcul de valeur et effet secondaire associé à B.

Lorsque vous avez des effets secondaires dans votre code, ils sont séquencés par différentes expressions. La règle dit qu'entre 2 points de séquence, vous pouvez permuter ces expressions comme vous le souhaitez.

Par exemple. i = i++. Étant donné qu'aucun des opérateurs impliqués dans cette expression ne représente des points de séquence, vous pouvez permuter les expressions qui sont des effets secondaires comme vous le souhaitez. Le langage C vous permet d'utiliser n'importe laquelle de ces séquences

i = i; i = i+1; Ou i = i+1; i=i; Ou tmp=i; i = i+1 ; i = tmp; Ou tmp=i; i = tmp; i = i+1; Ou tout ce qui donne le même résultat que le sémantique abstraite du calcul demande une interprétation de ce calcul. La norme ISO9899 définit le langage C comme une sémantique abstraite.

9
alinsoar

Il peut ne pas y avoir d'UB dans votre programme, mais dans la question: l'instruction int val = (++i > ++j) ? ++i : ++j; appelle-t-elle un comportement non défini?

La réponse est oui. L'une ou les deux opérations d'incrémentation peuvent déborder, puisque i et j sont signées, auquel cas tous les paris sont désactivés.

Bien sûr, cela ne se produit pas dans votre exemple complet car vous avez spécifié les valeurs sous forme de petits entiers.

5
Doug Currie

J'allais commenter sur @Doug Currie que le débordement d'entier signé était une friandise trop farfelue, bien que techniquement correct comme réponse. Au contraire!

Après une seconde réflexion, je pense que la réponse de Doug est non seulement correcte, mais en supposant un triplet pas tout à fait trivial comme dans l'exemple (mais un programme avec peut-être une boucle ou autre) devrait être étendu à un "oui" clair et précis. Voici pourquoi:

Le compilateur voit int i = 1, j = 2;, Donc sait que ++ i sera égal à j et ne pourra donc pas être plus grand que j ou même ++j. Les optimiseurs modernes voient ces choses triviales.

Sauf bien sûr, l'un d'eux déborde. Mais l'optimiseur sait que ce serait UB, et suppose donc que, et optimise selon, cela ne se produira jamais.

Ainsi, la condition de l'opérateur ternaire est toujours fausse (dans cet exemple facile certainement, mais même si elle est invoquée à plusieurs reprises dans une boucle, ce serait le cas!), Et i ne sera incrémenté que ne fois, tandis que j sera toujours incrémenté deux fois. Ainsi non seulement j est toujours plus grand que i, mais il gagne même à chaque itération (jusqu'à ce qu'un débordement se produise, mais cela ne se produit jamais selon notre hypothèse).

Ainsi, l'optimiseur est autorisé à transformer cela en ++i; j += 2; Sans condition, ce qui n'est sûrement pas ce à quoi on pourrait s'attendre.

La même chose s'applique par exemple une boucle avec inconn valeurs de i et j, comme une entrée fournie par l'utilisateur. L'optimiseur pourrait très bien reconnaître que la séquence des opérations ne dépend que des valeurs initiales de i et j. Ainsi, la séquence d'incréments suivie d'un mouvement conditionnel peut être optimisée en dupliquant la boucle, une fois pour chaque cas, et en basculant entre les deux avec une seule if(i>j). Et puis, pendant que nous y sommes, il pourrait plier la boucle d'incrémentation répétée par deux en quelque chose comme (j-i)<<1 Qu'il ajoute simplement. Ou quelque chose.
Dans l'hypothèse où le débordement ne se produit jamais - ce qui est l'hypothèse que l'optimiseur est autorisé à faire, et fait faire - une telle modification qui peut complètement changer complètement le sens et le mode du fonctionnement du programme est parfaitement bien.

Essayez de déboguer cela.

0
Damon