Bonjour,
Une question un peu tordu peut etre ?
Est ce qu'on peut faire ceci, est ce que cela a un comportement défini ?
MerciCode:
1
2
3
4
5
6
7 int a; [...] if( a == 0 && FonctionQuiChangeA(&a) && a == 0) { //du code }
Version imprimable
Bonjour,
Une question un peu tordu peut etre ?
Est ce qu'on peut faire ceci, est ce que cela a un comportement défini ?
MerciCode:
1
2
3
4
5
6
7 int a; [...] if( a == 0 && FonctionQuiChangeA(&a) && a == 0) { //du code }
Personnellement, je le ferai pas déjà pour ne pas avoir à me poser la question et m'enlever le mal de tête
et puis je ne sais pas comment l'optimiseur il va réagir en voyant
Même si il y a des trucs au milieu, il peut se dire (à tord dans ce cas) "la condition est toujours fausse" car a ne peut être égal à 0 et à 1 en même tempsCode:if( (a == 0) && (a == 1) )
C'est même certain !
Cette condition sera toujours évaluée à FALSE.
Certains compilo (avec l'option qui va bien) te le signalerons avec un warning (un truc du genre "conditional expression is constant").
Regle d'or : choisir l'option qui permet d'afficher tous les warning et n'en laisser aucun !
Comme ça, quand tu modifies ton code et qu'un nouveau warning apparaît, tu le vois tout de suite. :lun:
Hadrien
J'en doute. L'important ici, c'est FonctionQuiChangeA(&a). L'operateur && introduit un point de sequence, donc le compilateur ne peut pas simplifier le test. Puisqu'il est garanti que l'evaluation de l'expression se fait de gauche a droite, alors je ne vois aucun probleme avec ce code (sauf que c'est assez moche). Dans le meme ordre d'idee,
ouCode:c = getchar()) != EOF && c != '\n'
sont parfaitement surs.Code:p != NULL && *p == '\0'
Salut,
La règle de base est de toujours éviter les effets de bord lorsque tu peux...
[EDIT]Un effet de bord est le résultat que l'on obtient lorsqu'une instruction a pour effet de modifier une valeur que l'on est déjà occupé à traiter par ailleurs (définition simple mais suffisante :D)
[/EDIT]
L'opérateur logique && est, si c'est la même chose qu'en C++, optimisé.
Cela signifie que, dans le cas de l'opérateur &&, l'expression se trouvant à sa droite ne sera évaluée que si l'expression complète a encore une chance de donner un résultat à true.
Cela implique que, si la première expression (celle qui se trouve à gauche de l'opértateur &&) est fausse, la deuxième (celle qui se trouve à sa droite) ne sera jamais évaluée, et donc, que les modifications que tu peux attendre de cette expression (qui est ici un appel de fonction) risquent de n'être jamais effectuées.
En effet, pour l'opérateur logique &&, tu as une table de vérité qui prend la forme de
Dés lors, si tu considère que, dans ton expression i==0 && fonctionModifiantICode:
1
2
3
4
5 expr A | expr B | A && B false | false | false false | true | false true | false | false true | true | true
i==0 est l'epression A et que foncitonModifiantI est l'expression B, tu remarque qu'il n'y aura qu'un cas dont le résultat correspondra à true: le fait que A est vrai ET que B est vrai :P
Dés lors, si A est, dés le départ, faux, l'expression sera d'office considérée comme fausse, et l'expression B ne sera jamais évaluée... avec comme résultat que fonctionModifiantI ne sera jamais appelée (et i... jamais modifié)
Si tu as compris l'optimisation apportée par l'opérateur && (qui est, soit dit en passant similaire pour l'opérateur ||) et que tu es d'accord avec ma table de vérité (et je ne vois pas pourquoi tu ne le serais pas :aie:) tu comprend tous les risques que tu encoure à essayer d'utiliser un effet de bord pour la deuxième expression ;)
oui, et c'est l'effet voulu...
Je veux executer une fonction seulement si mavariable est à une certaine valeur, et ensuite executer du code si l'appel de ma fonction n'a pas changé ma valeur.
C'était un exemple, donc mon cas serait plutot celui ci :
Dans ce cas le risque éventuel serait que le compilateur ne fasse pas le troisieme test.Code:
1
2 if(a == 0 && maFonction(&a) && a == 0)
En toute logique, si maFonction renvoie une valeur qui peut être considérée comme fausse (AKA == 0), le troisième test risque effectivement de passer à la trappe...
Mais, quoi qu'il en soit, n'oublie pas que la première qualité d'un code, avant même de faire ce que l'on attend de lui, c'est d'être facilement lisible et compréhensible, y compris lors qu'une lecture en diagonale...
L'idéal à mon sens est donc de séparer les différents tests (de toutes manières, cela n'influera nullement sur les performances à l'heure acutelle), sous une forme proche de
Juste histoire de faire mentir les mauvaises langues qui crient que C est write once read never ;)Code:
1
2
3
4
5
6
7 if (i==) { if(maFonction) { /* retester i éventuellement ici */ } }
Bien sûr !Citation:
Envoyé par DaZumba
Selon la valeur de a, la l'appel à FonctionQuiChangeA se fera ou pas.
Mais je maintiens que cette condition sera toujours évaluée à FALSE et que donc, le code conditionel qui suit ne sera jamais executé.
PS : à l'époque de mon post, le code était :Mais depuis, il a été modifié, et mon commentaire n'a plus de sens...Code:
1
2
3
4 if( a == 0 && FonctionQuiChangeA(&a) && a == 1) { //du code }
Hadrien
Je ne suis pas d'accord avec toi...
D'abord parce que si tu te pose dans une situation où ta table de vérité donne un résultat vrai, ton expression sera bel et bien évaluée à vrai (si, pour le premier terme, a==0, que FonctionQuiChangeA renvoie vrai et si, après, malgré le changement a==0)
ensuite, il ne faut pas oublier que c'est une technique qui reste malgré tout souvent utilisée avec les pointeurs:
Ce n'est clairement pas le genre de code que je conseille, mais cela fait bel et bien partie des codes qui sont envisageables ;) (et les deux expressions sont évaluées si, à la base, ptr n'est pas nul ;))Code:
1
2
3
4
5
6
7
8
9
10
11 /* soit une structure proche de */ struct MaStruct { type data; struct MaStruct* next; }; /* dans une fonction, soit un pointeur de type MaStruct */ if(ptr && (ptr=ptr->next)!=NULL) { /* ce qui doit être fait */ }
EDIT Evidemment, si la fonction est sensée modifier a, le test devrait de préférence être au minimum différent :P
J'ai voulu tester ce que le compilo faisait exactement, en sortant du code assembleur. L'ordre des tests est bien respecté.
Voici le code C :
Et maintenant le code assembleur généré pour la ligne du "if" :Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 #include <stdio.h> #include <stdlib.h> #include <time.h> int FonctionQuiChangeA(int *a) { *a=1; return 1; } int main(void) { int a; srand(time(NULL)); a=rand()%3; if( a == 0 && FonctionQuiChangeA(&a) && a == 1) { printf("OK..."); } getchar(); return 0; }
Pour résumer :Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 .loc 1 14 0 mov eax, DWORD PTR [ebp-8] <- Correspond à la variable "a" test eax, eax <- 1ère condition : On teste si "a" est égal à 0 jne L4 <- On sort du "if" si "a" ne vaut pas 0 lea eax, [ebp-8] push eax LCFI10: call _FonctionQuiChangeA <- Appelle la fonction add esp, 4 test eax, eax <- 2ème condition : On teste si la valeur retournée vaut 0 je L4 <- Si la valeur retournée vaut 0, on sort du "if" mov eax, DWORD PTR [ebp-8] <- Correspond à la variable "a" cmp eax, 1 <- 3ème condition : On compare si "a" vaut 1 jne L4 <- Si "a" n'est pas égal à 1, on sort du "if" .loc 1 16 0 <- On est maintenant dans le bloc "if" sub esp, 12 push OFFSET FLAT:LC0 LCFI11: call _printf add esp, 16 L4: <- Sortie du bloc "if" .loc 1 19 0
1°) La première condition est testée en premier. Si le test échoue, on sort du "if", et les deux autres conditions ne seront pas testées.
2°) Sinon, on teste la seconde. Si le test échoue, on sort du "if" et la dernière condition ne sera pas testée.
3°) Sinon, on teste la troisième. Si le test échoue, on sort du "if", et on n'entre pas dans le bloc "if"
4°) Sinon, le bloc "if" est executé.
Non, c'était pour montrer la chose "en image". :mrgreen:Citation:
Et ça t'étonne ??
Et bien moi non plus, je ne suis pas d'accord avec moi !
Gros beignet que je suis !
Je n'avais même pas remarqué que la FonctionQuiChangeA pouvait changer a :oops:
C'est effectivement un code un peu pervers mais qui n'est pas dénué de sens...
Je m'au-temps-pour-moise et présente toutes mes confuses.
Hadrien