Votre question n'était probablement pas: "Pourquoi ces constructions ont-elles un comportement indéfini en C?". Votre question était probablement: "Pourquoi ce code (en utilisant ++
) ne m'a- t-il pas donné la valeur que j'attendais?", Et quelqu'un a marqué votre question en double et vous a envoyé ici.
Cette réponse tente de répondre à cette question: pourquoi votre code ne vous a-t-il pas donné la réponse que vous attendiez, et comment pouvez-vous apprendre à reconnaître (et éviter) les expressions qui ne fonctionneront pas comme prévu.
Je suppose que vous avez déjà entendu la définition de base des C ++
et des --
opérateurs, et comment la forme du préfixe ++x
diffère de la forme du postfix x++
. Mais ces opérateurs sont difficiles à penser, alors pour vous assurer que vous avez compris, vous avez peut-être écrit un tout petit programme de test impliquant quelque chose comme
int x = 5;
printf("%d %d %d\n", x, ++x, x++);
Mais, à votre grande surprise, ce programme ne vous a pas aidé à comprendre - il a imprimé une sortie étrange, inattendue, inexplicable, suggérant que peut-être++
fait quelque chose de complètement différent, pas du tout ce que vous pensiez qu'il faisait.
Ou, peut-être que vous regardez une expression difficile à comprendre comme
int x = 5;
x = x++ + ++x;
printf("%d\n", x);
Peut-être que quelqu'un vous a donné ce code comme un puzzle. Ce code n'a également aucun sens, surtout si vous l'exécutez - et si vous le compilez et l'exécutez sous deux compilateurs différents, vous obtiendrez probablement deux réponses différentes! Qu'est-ce qui se passe? Quelle réponse est correcte? (Et la réponse est que les deux le sont, ou aucun d'eux ne l'est.)
Comme vous l'avez déjà entendu, toutes ces expressions ne sont pas définies , ce qui signifie que le langage C ne garantit pas ce qu'elles feront. C'est un résultat étrange et surprenant, car vous pensiez probablement que tout programme que vous pourriez écrire, tant qu'il serait compilé et exécuté, générerait une sortie unique et bien définie. Mais dans le cas d'un comportement indéfini, ce n'est pas le cas.
Qu'est-ce qui rend une expression indéfinie? Les expressions impliquant ++
et--
toujours indéfinies? Bien sûr que non: ce sont des opérateurs utiles, et si vous les utilisez correctement, ils sont parfaitement bien définis.
Pour les expressions dont nous parlons, ce qui les rend indéfinies, c'est quand il y a trop de choses à la fois, quand nous ne savons pas dans quel ordre les choses vont se passer, mais quand l'ordre compte pour le résultat que nous obtenons.
Revenons aux deux exemples que j'ai utilisés dans cette réponse. Quand j'ai écrit
printf("%d %d %d\n", x, ++x, x++);
la question est, avant d'appeler printf
, le compilateur calcule-t-il la valeur dex
premier, ou x++
, ou peut ++x
- être ? Mais il s'avère que nous ne savons pas . Il n'y a pas de règle en C qui dit que les arguments d'une fonction sont évalués de gauche à droite, de droite à gauche ou dans un autre ordre. Donc , nous ne pouvons pas dire si le compilateur fera d' x
abord, puis ++x
, puis x++
, ou x++
alors ++x
alors x
, ou d' un autre ordre. Mais l'ordre est clairement important, car selon l'ordre utilisé par le compilateur, nous obtiendrons clairement des résultats différents imprimés par printf
.
Et cette expression folle?
x = x++ + ++x;
Le problème avec cette expression est qu'elle contient trois tentatives différentes pour modifier la valeur de x: (1) la x++
partie essaie d'ajouter 1 à x, de stocker la nouvelle valeur dans x
et de renvoyer l'ancienne valeur de x
; (2) la ++x
partie essaie d'ajouter 1 à x, de stocker la nouvelle valeur dans x
et de renvoyer la nouvelle valeur de x
; et (3) la x =
partie essaie de réattribuer la somme des deux autres à x. Laquelle de ces trois tentatives de mission "gagnera"? À laquelle des trois valeurs sera effectivement attribuée x
? Encore une fois, et peut-être de façon surprenante, il n'y a pas de règle en C pour nous le dire.
Vous pourriez imaginer que la priorité ou l'associativité ou l'évaluation de gauche à droite vous indique dans quel ordre les choses se produisent, mais ce n'est pas le cas. Vous ne me croyez peut-être pas, mais croyez-moi, je le redis: la priorité et l'associativité ne déterminent pas tous les aspects de l'ordre d'évaluation d'une expression en C. En particulier, si au sein d'une même expression, il y en a plusieurs différents endroits où nous essayons d'assigner une nouvelle valeur à quelque chose comme x
, la priorité et associativité ne pas nous dire que ces tentatives se premier ou dernier, ou quoi que ce soit.
Donc, avec tout cet arrière-plan et cette introduction à l'écart, si vous voulez vous assurer que tous vos programmes sont bien définis, quelles expressions pouvez-vous écrire et lesquelles ne pouvez-vous pas écrire?
Ces expressions sont toutes très bien:
y = x++;
z = x++ + y++;
x = x + 1;
x = a[i++];
x = a[i++] + b[j++];
x[i++] = a[j++] + b[k++];
x = *p++;
x = *p++ + *q++;
Ces expressions sont toutes indéfinies:
x = x++;
x = x++ + ++x;
y = x + x++;
a[i] = i++;
a[i++] = i;
printf("%d %d %d\n", x, ++x, x++);
Et la dernière question est, comment pouvez-vous dire quelles expressions sont bien définies et quelles expressions ne sont pas définies?
Comme je l'ai dit plus tôt, les expressions non définies sont celles où il y a trop de choses à la fois, où vous ne pouvez pas être sûr de l'ordre dans lequel les choses se passent et où l'ordre est important:
- S'il y a une variable qui est modifiée (affectée à) à deux endroits différents ou plus, comment savez-vous quelle modification se produit en premier?
- S'il y a une variable qui est modifiée à un endroit et dont la valeur est utilisée à un autre endroit, comment savez-vous si elle utilise l'ancienne ou la nouvelle valeur?
Comme exemple de # 1, dans l'expression
x = x++ + ++x;
il y a trois tentatives pour modifier `x.
Comme exemple de # 2, dans l'expression
y = x + x++;
nous utilisons tous les deux la valeur de x
et la modifions.
Voilà donc la réponse: assurez-vous que dans n'importe quelle expression que vous écrivez, chaque variable est modifiée au plus une fois, et si une variable est modifiée, vous n'essayez pas également d'utiliser la valeur de cette variable ailleurs.