Skip to content

Boucles for et virgules en C

6 décembre 2012
#include <stdio.h>
#include <stdlib.h>
int main(void){
     int i,j;
     for (i=0,j=0 ; i<4,j<3 ; i++,j++)
         printf("(%d,%d) ", i, j);
     return EXIT_SUCCESS;
}

Que fait ce programme ? Certains élèves semblent croire que cette forme de boucles for utilisant l’opérateur virgule est juste une forme abrégée équivalente à deux boucles imbriquées

Vraiment ? Comparons les deux formes.

Voici des boucles imbriquées habituelles :

#include <stdio.h>
#include <stdlib.h>
int main(void){
    int i,j;
    for (i=0 ; i<4 ; i++)
        for (j=0 ; j<3 ; j++)
            printf("(%d,%d) ", i, j);
    return EXIT_SUCCESS;
}

Rappels

Avant de parler de for, je voudrais faire quelques rappels.

Étapes de la traduction du C au langage machine

Quand on compile un programme, il y a deux phases :

  • le prétraitement du code source par le préprocesseur : c’est lui qui va (entre autre) remplacer toutes les directives #include <fichier.h> par le contenu de fichier.h et qui va remplacer toutes les constantes définies dans les directives #define CONSTANTE valeur par leurs valeurs
  • la compilation (proprement dite) par le compilateur : c’est la traduction du programme source (qui a été modifié par le préprocesseur) en langage machine pour obtenir un fichier binaire exécutable

Constituants d’un programme C

Pour le compilateur, le code d’un programme en C est constitué de 2 choses :

  • de déclarations, exemples : int varglobale[100] ; struct enr { int a ; float b ; } ; int premier(int n) ;
  • de définitions de fonctions (main() et autres), qui contiennent elles-mêmes des déclarations et des instructions.

Instructions

Les instructions sont de quatre genres :

  • les structures de contrôle : construites avec if … else, for, while, do...while, goto, etc.
  • les blocs, ou instructions composées : constituées d’une ou plusieurs instructions entourées d’accolades { }
  • l’instruction vide : constituée d’un point-virgule seul, qui dit : « ne fait rien ! » (si, si, c’est une instruction)
  • les instructions construites à partir d’une expression suivie d’un point-virgule.

C’est tout ? C’est tout, mais les expressions sont très riches et variées.

Expressions

Les expressions sont de 2 genres :

  • les expressions élémentaires, qui peuvent être :
    • une constante, exemples : -3.14 ; 'A' ; "hello",
    • une variable,
    • un nom de tableau (qui est converti par le compilateur en l’adresse du début de ce tableau),
    • un nom de fonction, exemples : strlen ; premier ; printf ;

Bien sûr, pour les trois derniers, ils doivent déjà avoir été déclarés.

  • les combinaisons d’opérations, faites à partir d’expressions (appelées opérandes) et d’opérateurs.

Remarque : Cette définition est récursive, puisqu’on peut combiner des expressions avec des opérateurs pour obtenir de nouvelles expressions.

Opérateurs

Les opérateurs sont de plusieurs genres :

  • Les opérateurs arithmétiques + – * / % qui produisent des résultats numériques si les opérandes sont de types numériques (+ et peuvent aussi être utilisés pour l’arithmétique des pointeurs)
  • Les opérateurs relationnels < > <= >= ==!= et logiques && || ! qui produisent des résultats… numériques : si l’expression est vraie, le résultat est 1, sinon c’est 0. Exemples : printf("%d", 2+3>6) ; affiche 0, et printf("%d",!0 && !(2+2 != 4)) ; affiche 1.

Remarque : toute valeur différente de 0 est considérée comme « vrai » et 0 est « faux ». Exemple : while(5) ; while(1); while(1+1==2) ; sont des boucles infinies équivalentes.

  • Les opérateurs d’arithmétique binaire & | ~ ^ << >> pour les calculs en base 2
  • L’opérateur ( ) pour modifier la priorité des opérateurs
  • L’opérateur ( ) pour l’appel d’une fonction
  • L’opérateur ( ) de conversion de type explicite (ou cast)
  • L’opérateur d’indiçage de tableau [ ] pour accéder aux éléments successifs à l’adresse donnée
  • L’opérateur d’indirection (ou de déréférencement) * pour accéder à l’élément pointé par l’opérande
  • L’opérateur de référencement & qui donne l’adresse de l’opérande
  • Les opérateurs de sélection de champ . et –> pour accéder aux champs d’une structure ou d’une union. (L’expression p->champ est exactement équivalente à (*p).champ)
  • L’opérateur ternaire ? : pour les expressions conditionnelles. Exemple : n>1 ? "s" : ""
  • L’opérateur virgule , pour faire une liste d’expressions.

Attention : les expressions séparées par des virgules sont toutes évaluées mais le résultat est la valeur de la dernière seulement. Exemple : int a=3 ; printf("%d ",(2,a++,7,5)) ; printf("%d",a) ; affiche 5 4

  • L’opérateur sizeof pour obtenir la taille mémoire de l’opérande
  • Les opérateurs à effet de bord :
    • opérateur d’affectation = : en C, contrairement à certains autres langages, une affectation est une expression, pas une instruction. On dit que c’est un opérateur à effet de bord car contrairement aux opérateurs précédents (sauf l’appel d’une fonction parfois), l’évaluation de l’expression obtenue avec cet opérateur ne fait pas que donner un résultat, mais modifie aussi un de ses opérandes (celui de gauche). Mais l’évaluation donne aussi un résultat : c’est la valeur de l’opérande de droite. Exemple : printf("%d", (b = 7)) ; affiche 7 après l’avoir affecté à b.
    • opérateurs d’autoincrémentation ++ et autodécrémentation — . Ils sont 4 :
      • pré-incrémentation :  ++a
      • post-incrémentation :  a++
      • pré-décrémentation :  --a
      • post-décrémentation :  a--

Que l’opérateur soit placé avant ou après, l’effet de bord est le même : l’opérande est modifié (incrémenté ou décrémenté). Quand l’opérateur est placé avant, la valeur de l’expression est celle de l’opérande après sa modification. Quand il est placé après, c’est celle de l’opérande avant sa modification.

Exemples : int b=7; printf("%d ",++b); printf("%d",b); affiche 8 8
int b=7; printf("%d ",b--); printf("%d",b); affiche 7 6

    • opérateurs d’affectation combinée += -= *= /= %= &= |= ^= <<= >>= donnent une expression dont le résultat est la valeur après modification

Rappel sur les boucles for

Passons maintenant aux boucles for. Entre les parenthèses d’une boucle for, il y a trois expressions, séparées par des points-virgules :

1. La première expression est évaluée juste avant la première itération. La valeur de cette expression n’est pas utilisée. On y met généralement une opération d’affectation pour initialiser le compteur.

2. La deuxième expression est évaluée avant chaque itération. Sa valeur permet de décider si l’itération doit être effectuée (la boucle continue) ou pas (arrêt des itérations). Si c’est 0 on arrête, si ce n’est pas 0 on continue. Elle contient en général la condition de contrôle de la boucle.

3. La troisième expression est évaluée à la fin de chaque itération. Comme pour la première, sa valeur n’est pas récupérée. Elle permet en général d’incrémenter le compteur.

Donc, les expressions 1 et 3 doivent avoir un effet de bord, sinon elles ne servent à rien, mais l’expression 2 n’en a généralement pas (même si ce n’est pas interdit).

Finalement…

Revenons (enfin) à notre programme et ses boucles for imbriquées :

    for (i=0 ; i<4 ; i++)
        for (j=0 ; j<3 ; j++)
            printf("(%d,%d) ", i, j);

1re boucle :
i = 0 ; i < 4 oui car 0 < 4 => on continue
2e boucle :
j = 0 ; j < 3 oui car 0 < 3 => on continue
printf(« (%d,%d) « , i, j); affiche (0,0)
j++ ; j = 1
j < 3 oui car 1 < 3 => on continue
printf(« (%d,%d) « , i, j); affiche (0,1)
j++ ; j = 2
j < 3 oui car 2 < 3 => on continue
printf(« (%d,%d) « , i, j); affiche (0,2)
j++ ; j = 3
j < 3 non car 3 < 3 est faux => fin de la 2e boucle
1re boucle (suite) :
i++ ; i = 1
i < 4 oui car 1 < 4 => on continue
2e boucle :
j = 0 ; j < 3 oui car 0 < 3 => on continue
printf(« (%d,%d) « , i, j); affiche (1,0)
j++ ; j = 1
j < 3 oui car 1 < 3 => on continue
printf(« (%d,%d) « , i, j); affiche (1,1)
j++ ; j = 2
j < 3 oui car 2 < 3 => on continue
printf(« (%d,%d) « , i, j); affiche (1,2)
j++ ; j = 3
j < 3 non car 3 < 3 est faux => fin de la 2e boucle
1re boucle (suite) :
i++ ; i = 2
i < 4 oui car 1 < 4 => on continue
2e boucle :
j = 0 ; j < 3 oui car 0 < 3 => on continue
printf(« (%d,%d) « , i, j); affiche (2,0)
etc…
L’exécution de ce programme affiche donc :
(0,0) (0,1) (0,2) (1,0) (1,1) (1,2) (2,0) (2,1) (2,2) (3,0) (3,1) (3,2)

Et le premier programme ? Celui avec la boucle for unique et l’opérateur virgule ?

  for (i=0,j=0 ; i<4,j<3 ; i++,j++)
      printf("(%d,%d) ", i, j);

i = 0, j = 0
i<4, j<3 donne 1 (vrai) car 0 < 3 n’oublions pas que seule la 2e (dernière) expression après la virgule donne sa valeur à l’ensemble => on continue
printf(« (%d,%d) « , i, j); affiche (0,0)
i++, j++ ; i = 1, j = 1
i<4, j<3 donne 1 (vrai) car 1 < 3 => on continue
printf(« (%d,%d) « , i, j); affiche (1,1)
i++, j++ ; i = 2, j = 2
i<4, j<3 donne 1 (vrai) car 2 < 3 => on continue
printf(« (%d,%d) « , i, j); affiche (2,2)
i++, j++ ; i = 3, j = 3
i<4, j<3 donne 0 (faux) car 3 < 3 faux => on arrête

L’exécution de ce programme affiche donc :

(0,0) (1,1) (2,2)

Ce qui est complètement différent de l’autre… CQFD

Si on compile ce programme avec gcc et l’option -Wall de cette manière :

gcc forvirgule.c -o forvirgule -Wall

alors on reçoit l’avertissement :

forvirgule.c:5:21: attention : l'opérande à gauche de la virgule n'a pas d'effet [-Wunused-value]

Nous l’avions bien remarqué: i<4,j<3 c’est la même chose que j<3. L’expression i<4 est évaluée avant j<3 mais sa valeur n’est pas utilisée, et comme elle n’a pas d’effet de bord, elle ne sert strictement à rien.

Références :

Publicités

From → C, Programmation

2 commentaires
  1. EPST _ Teacher permalink

    Chers amis,

    au lieu d’utiliser la virgule pour séparer les deux conditions i<4, j<3 il est préférable d'utiliser l'opérateur logique && ou bien || car la virgule ne permet pas de combiner plusieurs conditions dans la boucle.

    J'aime

    • Merci pour ton commentaire, cher ami :-)
      Je suis d’accord avec toi, c’est d’ailleurs ce que j’ai expliqué à plusieurs élèves qui avaient fait l’erreur. Si on veut vraiment que le test de contrôle de la boucle prenne en compte ces deux conditions, c’est soit la première ET la deuxième, soit la première OU la deuxième, ce qui en C s’écrit avec && et || respectivement, pas avec une virgule.
      Cependant, dans l’exemple que j’ai cité au début de cet article, le problème n’est pas là : même en mettant && ou || entre les deux conditions, on n’obtient pas un résultat satisfaisant. Si on met &&, ça ne change rien à l’exécution : quand j < 3 devient fausse, la boucle se termine. Avec ||, il faudra que i < 4 devienne fausse pour que la boucle s'arrête, cela donnera donc une itération de plus. Mais de toute façon, cela revient à la même chose que faire :

      for (i=0 ; i<4 ; i++)
      printf("(%d,%d) ", i, i);

      ce qui est plus simple et élimine une variable inutile.

      J'aime

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

%d blogueurs aiment cette page :