cursus.

Cours 2 · Structures de contrôle et fonctionsLeçon 1 sur 2

Contrôle du flux

5 h de lecture8 sections Version PDF

À la fin de cette leçon, vous saurez

if et switch, while, do…while et for, break et continue ; traduire en C le pseudocode d'Algorithmique 1.

En février 2014, Apple corrige en urgence une faille présente dans iOS et macOS. Le code fautif vérifiait un certificat TLS, et il contenait ceci :

if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)    goto fail;    goto fail;                    /* ← cette ligne est en trop */if ((err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0)    goto fail;

La seconde ligne goto fail n'appartient à aucun if : l'indentation le suggère, mais il n'y a pas d'accolades, donc un if sans accolades ne gouverne que l'instruction suivante. Le saut est donc inconditionnel, toute la suite de la vérification est sautée, et la fonction rend err = 0, c'est-à-dire « certificat valide ». N'importe quel certificat.

Ce chapitre traite des structures de contrôle du C. Elles ne présentent aucune difficulté conceptuelle pour qui a suivi Algorithmique 1 — ce sont les mêmes — mais leur syntaxe recèle une poignée de pièges dont celui-ci est le plus célèbre.

Une condition est un entier

C'est le point de départ, et il explique presque tous les pièges qui suivent. Le C n'a pas de type booléen dans sa version historique : une condition est une expression entière, où zéro est faux et toute autre valeur est vraie.

if (3)        /* vrai */if (0)        /* faux */if (-1)       /* vrai : seul zéro est faux */

Trois conséquences pratiques.

if (x = 5) compile. L'affectation est une expression, dont la valeur est 5, donc la condition est vraie — et x a été modifié au passage. C'est l'erreur de frappe la plus coûteuse du C, et -Wall la signale. Certains l'évitent en écrivant if (5 == x), la constante à gauche : une faute de frappe donnerait alors une erreur de compilation.

if (strcmp(a, b)) est inversé par rapport à l'intuition. strcmp rend 0 quand les chaînes sont égales, donc cette condition est vraie quand elles diffèrent. On écrit if (strcmp(a, b) == 0) pour tester l'égalité.

Enfin, stdbool.h fournit depuis C99 bool, true et false. Ce n'est qu'un habillage — true vaut 1 — mais il rend les intentions lisibles, et on l'emploiera.

Conditionnelles

if (note >= 16) {    printf("Très bien\n");} else if (note >= 14) {    printf("Bien\n");} else if (note >= 10) {    printf("Passable\n");} else {    printf("Insuffisant\n");}

L'ordre des tests décide de tout — c'est déjà l'exercice de la cascade d'Algorithmique 1 : le test le plus exigeant en premier, sans quoi les suivants deviennent inatteignables.

Et la règle que l'introduction impose : toujours mettre les accolades, même pour une seule instruction. Le coût est nul, le bénéfice est d'éliminer une classe entière de bogues — celui d'Apple, et celui, plus banal, d'ajouter une ligne à un if sans s'apercevoir qu'elle en sort.

Le switch compare une expression entière à des constantes :

switch (choix) {    case 1:        printf("Ajouter\n");        break;                    /* sans lui, on continue sur le cas 2 */    case 2:        printf("Supprimer\n");        break;    default:        printf("Choix inconnu\n");}

Le piège est le glissement (fallthrough) : sans break, l'exécution continue dans le cas suivant. Ce n'est pas un défaut de conception mais une fonctionnalité — elle permet de regrouper plusieurs cas partageant un traitement —, seulement l'oubli est fréquent, et gcc propose -Wimplicit-fallthrough pour le signaler.

Deux limites du switch : l'expression doit être entière (pas de chaînes, pas de flottants), et les étiquettes doivent être des constantes. Un test sur un intervalle demande un if.

Boucles

Les trois boucles du C se distinguent par le moment du test.

while (condition) { ... }         /* test AVANT : zéro tour possible */ do { ... } while (condition);     /* test APRÈS : au moins un tour */ for (init; condition; incrément) { ... }

Le for du C est plus général qu'il n'y paraît : ses trois parties sont des expressions quelconques, et chacune peut être vide. for (;;) est une boucle infinie parfaitement idiomatique.

Le do...while a un usage précis : la saisie contrôlée, où l'on doit demander au moins une fois avant de pouvoir tester. C'est aussi le seul dont le point-virgule final est obligatoire, et l'oublier produit un message d'erreur particulièrement obscur.

Deux fautes classiques méritent d'être nommées.

Le point-virgule fantôme : for (int i = 0; i < n; i++); suivi d'un bloc. La boucle a pour corps l'instruction vide, elle tourne n fois sans rien faire, puis le bloc s'exécute une seule fois. Aucun avertissement par défaut.

Le compteur non modifié : oublier l'incrément dans un while donne une boucle infinie — c'est la faute déjà signalée dans le chapitre 6 d'architecture, où l'incrément est explicite en assembleur.

Enfin, break sort de la boucle la plus interne et continue passe au tour suivant. Sortir de deux boucles imbriquées demande donc un drapeau, une fonction, ou — cas où il est défendable — un goto.

Quiz · vérifiez votre compréhension Sans réponse

Que fait ce code ? int x = 3 ; if (x = 0) printf('nul') ; else printf('%d', x) ;

Traduire le pseudocode d'Algorithmique 1

C'est l'exercice central du chapitre, et il est presque mécanique.

PseudocodeC
Lire(x)scanf("%d", &x);
Écrire(x)printf("%d\n", x);
x ← 5x = 5;
Si c Alors ... FinSiif (c) { ... }
Pour i de 1 à nfor (int i = 1; i <= n; i++)
Tant que c Fairewhile (c) { ... }
T[i] (indices de 1 à n)T[i-1] (indices de 0 à n−1)

La dernière ligne est la seule vraie difficulté, et elle vaut un avertissement. Le pseudocode du cours numérote souvent les tableaux à partir de 1 ; le C numérote à partir de 0. Une traduction distraite produit soit un décalage d'un rang, soit un débordement d'une case — les deux fautes les plus fréquentes du bloc III, et le C ne signalera ni l'une ni l'autre.

La bonne pratique n'est pas de traduire T[i] en T[i-1] mais de réécrire les bornes : for (int i = 0; i < n; i++), avec un < strict. Cet idiome donne exactement n tours et des indices valides de 0 à n−1 ; il vaut mieux l'adopter une fois pour toutes que de compter à chaque boucle.

Quiz · vérifiez votre compréhension Sans réponse

Un switch sur un menu affiche « Ajouter » puis « Supprimer » quand l'utilisateur choisit 1. Quelle est la cause ?

À vous

L'exercice traduit du pseudocode en C — simulé en JavaScript, dont la syntaxe de contrôle est assez proche pour que la traduction reste fidèle — puis reproduit les trois pièges du chapitre.

D'abord la classe de bogue d'Apple : un bloc conditionnel sans accolades auquel on ajoute une ligne, et l'effet sur le résultat. Ensuite le glissement de switch, avec le cas où il est volontaire et celui où il est fautif. Enfin le point-virgule fantôme après un for.

La dernière partie est la plus utile : la traduction d'un algorithme d'Algorithmique 1 — somme, maximum, recherche — avec des indices à partir de 0, et un test qui vérifie qu'aucune case n'a été lue hors bornes.

Exercice · JavaScript · à vous de jouer

Reproduisez le bogue « goto fail », le glissement de switch, et corrigez une traduction d'indices.

En attente
// ── 1. La classe de bogue « goto fail » ───────────────────────────────────
// Un if sans accolades ne gouverne QUE l'instruction suivante.
function verifier(etapes) {
  let erreur = 0;
  for (const e of etapes) {
    if ((erreur = e.resultat) !== 0)
      return "ÉCHEC à l'étape " + e.nom;
      // ← quelqu'un a dupliqué la ligne ci-dessus lors d'une fusion.
      //   Ajoutez « return "ÉCHEC (ligne en trop)"; » ici et observez.
  }
  return "certificat valide";
}

// ── 2. Glissement de switch ───────────────────────────────────────────────
function menu(choix) {
  const sortie = [];
  switch (choix) {
    case 1:
      sortie.push("Ajouter");
      // ← break manquant
    case 2:
      sortie.push("Supprimer");
      break;
    case 3:
    case 4:
      sortie.push("Lister");   // glissement VOLONTAIRE : 3 et 4 font pareil
      break;
    default:
      sortie.push("Choix inconnu");
  }
  return sortie.join(" puis ");
}

// ── 3. Traduire le pseudocode d'Algorithmique 1 ───────────────────────────
// Pseudocode, indices de 1 à n :
//     max ← T[1]
//     Pour i de 2 à n Faire
//         Si T[i] > max Alors max ← T[i]
//
// Traduction en C, indices de 0 à n−1. Le tableau est instrumenté : tout
// accès hors bornes est enregistré au lieu de passer inaperçu.
function tableauSurveille(valeurs) {
  const hors = [];
  return {
    n: valeurs.length,
    lire(i) {
      if (i < 0 || i >= valeurs.length) { hors.push(i); return undefined; }
      return valeurs[i];
    },
    horsBornes: () => hors,
  };
}

function maximum(T) {
  let max = T.lire(1);          // ← traduction distraite de T[1]
  for (let i = 2; i <= T.n; i++) {
    if (T.lire(i) > max) max = T.lire(i);
  }
  return max;
}

// ── À VOUS ────────────────────────────────────────────────────────────────
// 1. Ajoutez la ligne en trop dans verifier() et constatez l'effet.
// 2. Complétez menu() pour que le choix 1 n'affiche que « Ajouter ».
// 3. Corrigez maximum() : bornes de 0 à n−1, avec un < strict. Vérifiez
//    qu'aucun accès hors bornes n'est enregistré.

const T = tableauSurveille([3, 17, 8, 42, 5]);
console.log("maximum :", maximum(T), "| hors bornes :", JSON.stringify(T.horsBornes()));

Console de sortie
Le résultat s'affiche dans la console

En travaux pratiques

Travaux pratiques 3 · sur machine

Les pièges du contrôle

Rencontrer, dans du code qui compile sans le moindre avertissement, les quatre erreurs de structure qui coûtent le plus de temps en C.

2 h
Avant de commencer
  • Les TP 1 et 2
  1. 1. Le point-virgule

    Écrivez une boucle qui compte de 1 à 10 en plaçant un point-virgule juste après la parenthèse fermante du for. Compilez avec -Wall et exécutez. Que se passe-t-il ?

  2. 2. L'affectation dans le test

    Écrivez un si qui compare une variable à 5 en utilisant un seul signe égal. Exécutez. Puis trouvez l'écriture qui aurait fait échouer la compilation.

  3. 3. Le switch sans break

    Écrivez un aiguillage à quatre cas sans aucun break. Exécutez avec chaque valeur et notez les sorties. Trouvez ensuite un cas où l'absence de break est VOULUE.

  4. 4. Le bloc manquant

    Écrivez un si sans accolades suivi de deux instructions indentées identiquement. Exécutez, et expliquez pourquoi l'indentation vous a menti.

  5. 5. Boucle et condition composée

    Écrivez une recherche dans un tableau qui s'arrête dès qu'elle trouve. Écrivez-la de trois façons : avec break, avec un drapeau, avec la condition dans le while. Comparez la lisibilité.

  6. 6. Au fil rouge

    Ajoutez à journal le comptage des lignes contenant un mot donné, avec un rapport final. Testez sur un fichier vide, un fichier d'une ligne, et un gros fichier.

C'est réussi quand
  • Vous savez expliquer sans exécuter ce qu'affiche une boucle terminée par un point-virgule
  • Vous écrivez vos comparaisons de façon qu'un signe égal oublié ne compile pas
  • Votre journal donne le bon compte sur un fichier vide

Ce que la suite en fait

Le chapitre 4 découpe le programme en fonctions, ce qui introduit la vraie difficulté du bloc II : le passage par valeur. Une fonction reçoit des copies, donc elle ne peut pas modifier les variables de son appelant — et le premier réflexe, écrire une fonction echanger(a, b), ne fonctionne pas. La solution demande le bloc IV.

Les boucles reviendront aussi au chapitre 5 : parcourir un tableau est la construction la plus fréquente du langage, et c'est là que le décalage d'indice se paie.

À retenir

Flashcards · 1 / 4Toucher pour retourner
Fin de la leçon

Vous avez parcouru les 8 sections.

Marquez-la terminée pour faire avancer votre parcours, ou revenez sur un point avant de passer à la suite.