- Structure de contrôle
-
En programmation impérative, une structure de contrôle est une commande qui contrôle l'ordre dans lequel les différentes instructions d'un algorithme ou d'un programme informatique sont exécutées.
On appelle aussi cet enchaînement d'instructions le flot d'exécution d'un programme.
Sommaire
Structures de contrôle séquentielles
Un programme informatique est une suite d'instructions. Un registre interne du microprocesseur, le compteur ordinal (PC) est chargé de mémoriser l'adresse de la prochaine instruction à exécuter.
Séquence
La plupart des instructions d'un programme sont exécutées séquentiellement: après le traitement de l'instruction courante le compteur ordinal est incrémenté, et l'adresse de la prochaine instruction est chargée.
La première des structures de contrôle est donc la séquence. Elle permet l'évaluation ordonnée d'une suite d'expressions, souvent séparées par un point-virgule ou par des retours chariots.
Arrêt du programme
Un programme s'arrête généralement après l'exécution de la dernière instruction. La plupart des langages de programmation proposent également une ou plusieurs instructions pour stopper l'exécution du programme à une position arbitraire.
Selon l'environnement d'exécution sous-jacent (système d'exploitation ou microprocesseur), cet arrêt peut être définitif ou une suspension de l'exécution du programme en attendant un évènement externe. C'est par exemple le fonctionnement habituel de la plupart des instructions d'entrée sorties qui bloquent le flot d'exécution jusqu'à ce que le périphérique ait terminé de traiter les données.
Structures de contrôle itératives
Ces instructions permettent de réaliser une machine à états finis, cela signifie que leur seul effet de bord est de modifier un registre qui correspond à l'état courant du programme.
Dans un processeur, cet état correspond à la valeur du compteur ordinal.
Commandes à étiquettes
Une étiquette est un nombre ou un identificateur associé à une instruction du code source. Il est destiné à servir de cible à une structure de contrôle située ailleurs dans le programme. Hormis ce rôle de localisation, une étiquette n'a aucun effet sur le programme: elle ne modifie pas le comportement de l'instruction à laquelle elle est associée.
Les numéros de lignes sont un type d'étiquettes utilisées par des langages comme le Fortran ou le BASIC. Présentes devant les lignes du code source, elles doivent dans la plupart de ces langages augmenter au fur et à mesure du programme mais sans être forcément contiguës. Par exemple en BASIC:
10 X = 3 20 PRINT X
Dans d'autres langages, les étiquettes sont des identificateurs alphanumériques, qui se placent généralement en début de ligne et qui sont suivis par un double point. Par exemple en langage C:
success: printf ("The operation was successful.\n");
Il existe deux familles d'instructions qui permettent d'adresser ces étiquettes: les sauts inconditionnels, et les sauts conditionnels.
Sauts inconditionnels
Un saut inconditionnel, souvent appelé goto, permet de renvoyer l'exécution vers une étiquette. Ce saut est systématique, il entraine une rupture du flot d'exécution, on dit aussi 'rupture de séquence' ou branchement. L'instruction qui suit le saut dans le programme ne pourra donc elle-même être atteinte que par un autre saut.
Instruction 1 Saut Label1 Instruction 2 ... Label1: Instruction n
Exemples d'implémentations:
- BASIC : GOTO etiquette
- Asm 86: JMP etiquette
Sauts conditionnels
Les sauts conditionnels permettent de réaliser un branchement si une condition est vérifiée. Si la condition n'est pas vérifiée, l'exécution se poursuit séquentiellement. La condition est parfois appelée "condition de rupture" puisqu'elle implique en général une rupture dans le flot d'exécution lorsque la condition est vérifiée.
Dans un langage de haut niveau, cette condition est le résultat de l'évaluation d'une expression. En langage assembleur, c'est un indicateur : le bit d'un registre spécifique qui peut être modifié par d'autres instructions du langage.
Exemples d'implémentations :
- BASIC : IF condition GOTO etiquette
- Asm 86: JZ etiquette
Autres commandes à étiquettes
D'autres structures de contrôle qui seront présentées plus loin utilisent également la notion d'étiquette.
C'est par exemple le cas des appel de sous-programmes ou des commandes de sortie de boucle.
Commandes de blocs
Formellement, le saut conditionnel est la seule instruction nécessaire pour réaliser une machine à états finis. Si la plupart des processeurs supportent également, pour des raisons de performances, le saut inconditionnel, ce sont souvent les seules utilisées à ce niveau.
Les créateurs de langages de programmation ont cependant rapidement estimé que les étiquettes étaient peu adaptées aux mécanismes cognitifs de l'être humain. Souvent qualifiés de programmation spaghetti, les programmes utilisant de nombreuses instructions de saut sont difficiles à comprendre et donc à maintenir par les programmeurs. Ceux-ci peinent en effet à en percevoir la sémantique et sont obligés de dérouler mentalement les différents états de ce programme en le parcourant d'étiquettes en étiquettes.
Sont donc apparus de nouveaux jeux d'instructions de contrôle qui ne travaillent plus avec des étiquettes, mais sur des blocs d'instructions. D'un langage à l'autre, un grand nombre de commandes de blocs ont été inventées, mais on peut globalement les classer en deux grandes familles:
- les alternatives: exécuter un bloc d'instructions si une certaine condition est réunie.
- les boucles : exécuter un bloc d'instructions à plusieurs reprises.
Une telle démarche relève de la programmation structurée.
Blocs d'instructions
Un bloc d'instruction regroupe plusieurs instructions contiguës. Si l'on considère la façon de déclarer ces blocs, il existe deux grandes classes de langages:
Dans la première famille les instructions de contrôle sont composées de deux mots clef: un mot initial qui marque le début et un mot final qui indique la fin du bloc d'instructions; la forme du mot final varie d'un langage à l'autre:
- Ada: le mot final est end + espace + mot initial ex: if ... end if, loop ... end loop
- Algol 68, bash: mot initial en miroir : ex: if ... fi, case ... esac
- Fortran 77: end + mot initial ex: IF ... ENDIF, DO ... ENDDO
- Modula-2: le même mot final end pour toutes les structures de contrôle.
- Turbo Basic: chaque structure de contrôle à son propre mot final: if ... end if; for ... next; while ... wend
Dans la seconde famille de langages, les instructions de contrôle opèrent par défaut sur une instruction atomique. Le langage définit un autre mécanisme pour déclarer des blocs qui peuvent être utilisés avec ces instructions:
- des mots clefs - Algol 60 et Pascal : begin ... end; PL/1: DO ... END
- la ponctuation - C, C++, Java, Perl, et PHP: accolades { ... }; BCPL : $( ... $)
- la mise en page - Haskell et Python: indentation comme syntaxe
Alternatives
Les alternatives sont des structures de programmation effectuant un test logique sur une condition et permettant un choix entre divers blocs d'instructions suivant le résultat de ce test. La condition est en général appelée 'condition de continuation' car le flot d'exécution continue avec le bloc d'instruction qui suit immédiatement la condition lorsque celle-ci est vrai.
Test si
Le test si est la forme d'alternative la plus simple: si Test est vérifié on exécute Instruction 1 puis Instruction 2; si Test n'est pas vérifié on exécute directement 'Instruction 2
Pseudo code:
SI Test Instruction 1 FIN SI Instruction 2
Exemples d'implémentations:
- BASIC : IF condition THEN instruction
- Python : if condition : instruction
- Forth : condition IF instruction THEN
- PHP : IF(condition) { instruction(s) } (Note : les accolades ne sont pas indispensables s'il n'y a qu'une instruction.)
Le mot clef if correspond à si en anglais ; le mot clef then correspond à alors.
Test si sinon
SI Test Instruction 1 SINON Instruction 2 FIN SI Instruction 3
Si Test est vérifié on exécute Instruction 1 puis Instruction 3; sinon on exécute Instruction 2 puis Instruction 3.
Exemples d'implémentations:
- Pascal : if(condition) then instruction else 'instruction ;
- C/C++ : if(condition) instruction ; else 'instruction ;
Le mot clef else correspond à sinon en anglais. Historiquement, on rencontre un premier if then else dans Algol 60.
Test sinon si
SI Test 1 Instruction 1 SINONSI Test 2 Instruction 2 FIN SI Instruction 3
Si Test 1 est vérifié on exécute Instruction 1 puis Instruction 3; sinon si Test 2 est vérifié on exécute Instruction 2 puis Instruction 3; sinon on exécute directement Instruction 3.
On peut enchaîner autant d'instructions sinon si que désiré: seule la première dont la condition sera vérifiée sera exécutée. On peut généralement associer une clause sinon qui ne sera exécutée que si aucune clause sinon si n'a été vérifiée.
Dans les langages où la définition des blocs d'instruction est indépendante des structures de contrôle, cette instruction est redondante puisqu'elle revient à enchaîner un si après un sinon. Par exemple en langage C on écrira:
if( test1() ){ Instruction1(); } else if ( test2() ){ Instruction2(); } Instruction3();
Certains de ces langages définissent tout de même une instruction sinon si (par exemple elseif en PHP), pour des raisons de performance par exemple.
Test selon
Le test selon est une spécialisation de l'instruction sinon si, qui permet de sélectionner le bloc à exécuter en fonction de la valeur d'une variable. Il est utilisé lorsqu'un aiguillage offre plusieurs sorties, et que l'on doit tester une condition plusieurs fois, en utilisant toujours la même variable.
SELON Variable 1 CAS Valeur 1: Instruction 1 CAS Valeur 2: Instruction 2 FIN SELON Instruction 3
Structurellement, c'est équivalent à une succession de sinon si, mais le fait de savoir que la valeur de la variable testée ne changera pas lors de l'évaluation des conditions permet au compilateur de faire quelques optimisations.
Suivant les langages, l'expression qui suit le mot clef cas peut être une simple valeur pour un test d'égalité, une collection, un intervalle, ou une expression rationnelle par exemple.
Le switch case du langage C s'écarte sensiblement du modèle ci-dessus. Son comportement fall though (passage à travers) des case non terminé par le mot clef break, le rapproche plus d'une structure à étiquette.
Le switch case apparaît dans Algol 68.
Boucles
Une boucle est une structure de contrôle destinée à exécuter une portion de code plusieurs fois de suite, la structure de contrôle branchant le pointeur ordinal au début du code tant qu'une condition de continuation est remplie ou, selon les boucles, qu'une condition de sortie n'est pas remplie.
Normalement, une boucle s'exécute selon le cas, soit un nombre de fois connu à l'avance, soit jusqu'à ce qu'une condition permette de sortir de la boucle. Il arrive toutefois qu'une erreur de programmation fasse qu'un programme s'exécute indéfiniment à l'intérieur d'une boucle. On dit que le programme est en train de boucler.
Fortran II a introduit les boucles en 1958.
Boucle tant que
TANTQUE Test Instruction 1 FIN TANTQUE Instruction 2
Si Test est vérifié on exécute Instruction 1, puis arrivé à la fin du bloc on évalue à nouveau Test et on recommence. Quand Test renvoie un résultat faux on quitte la boucle en sautant à instruction 2. Le Test est en général appelé 'condition de continuation' puisque le flot d'exécution continue avec Instruction 1 lorsque le Test est vrai.
Exemples d'implémentations:
- C : while(condition) instruction ;
Le mot clef while en anglais correspond à tant que en français .
Boucle jusqu'à ce que
REPETE Instruction 1 JUSQUACEQUE Condition 1 Instruction 2
Cette boucle permet de réitérer une instruction ou un ensemble d'instructions jusqu'à ce qu'une condition de sortie soit vérifiée (par exemple en Pascal on boucle jusqu'à ce que la condition devienne vraie, alors qu'en C on boucle tant que celle-ci est vraie). La série d'instructions est exécutée au moins une fois, quelle que soit la condition.
Exemples d'implémentations:
- C : do instruction while(condition) ;
- Pascal : repeat instruction until condition ;
Le mot clef until correspond au français jusqu'à ce que, repeat à répète.
Commandes de sortie de boucle
Ce sont des instructions comme le break du langage C.
En ADA la commande exit, qui est équivalente au break accepte un identificateur, ce qui permet de l'utiliser dans des boucles imbriquées. Ce langage définit aussi une instruction exit when qui permet d'associer une condition.
Ces commandes s'utilisent en effet généralement au milieu d'une boucle pour réaliser une sortie anticipée de la boucle quand une condition est vérifiée.
Le langage C définit aussi une instruction continue qui force le pointeur de commande à revenir au début de la boucle.
Toutes ces commandes sont une forme de saut inconditionnel : si elles n'utilisent pas d'étiquettes formelles, elles permettent en effet de modifier la valeur du pointeur ordinal pour adresser un endroit spécifique du programme.
Le langage python permet d'associer une clause else à une boucle, pour effectuer un traitement personnalisé en cas de sortie normale d'une boucle.
Extensions de la notion de boucles
Il existe d'autres variantes de boucle, leur comportement est identique à celle d'une boucle tant que, mais elles nécessitent des variables additionnelles au registre PC pour mémoriser leur état.
Compteurs
POUR compteur DE 0 à fin Instruction 1 FIN POUR Instruction 2
Un compteur permet de réaliser une boucle associée à une variable entière ou un pointeur qui sera incrémentée à chaque itération. Il est souvent utilisé pour exploiter les données d'une collection indexée.
Exemples d'implémentations:
- BASIC : FOR compteur = depart TO fin instruction NEXT compteur
- Pascal : for compteur := depart to fin do instruction ;
Fortran II a introduit un premier compteur rudimentaire en 1958.
La notion de compteur peut être directement supportée par le microprocesseur, c'est le cas par exemple avec l'instruction LOOP de l'assembleur 86
Suivant les langages on peut fixer la valeur de début, de fin et la valeur d'incrément à chaque itération. Un de ses usages typique étant de servir d'index, la valeur du compteur est généralement accessible depuis l'intérieur de la boucle.
La boucle for des langages C,C++ ou Java est beaucoup plus générale qu'un simple compteur.
Itérateurs
POURCHAQUE valeur DANS collection Instruction 1 FIN POURCHAQUE Instruction 2
Un itérateur (ou curseur ou énumérateur) est un objet qui permet de réaliser une boucle parcourant tous les éléments contenus dans une structure de données.
Des langages comme le C++ ou l'Objective C (dans la version initiale du langage) n'ont pas de mots clefs spécifiques pour les itérateurs, ils utilisent des méthodes de bibliothèque associées à des structures de contrôles plus générales.
exemple en Objective C:
NSEnumerator* enumerator = [collection objectEnumerator]; NSObject* obj; while(obj = [enumerator nextObject]){ NSLog(@"%@", obj); }
Dans d'autres langages, ce concept bénéficie de structures de contrôle dédiées. Exemples d'implémentations:
- Python : for valeur in collection : instruction
- Perl : foreach $valeur (collection) instruction ;
Sous-programmes
Dans les années 1950, la mémoire des ordinateurs était onéreuse et les sous-programmes étaient principalement utilisés pour réduire la taille des programmes: un ensemble d'instructions était écrit une fois et pouvait être appelé depuis plusieurs endroits du programme.
Le support des sous-programmes a également permis aux ordinateurs d'implémenter des algorithmes récursifs.
Exemple de sous-programme en basic
10 X = 2 19 FARG = X 20 GOSUB 50 22 Y = FRET 30 PRINT Y 40 END 48 '------------------------------------ 49 ' Double la valeur passée en argument 50 FRET = FARG*2 60 RETURN
Dans cet exemple, à la ligne 20 la valeur courante du compteur ordinal est archivée puis le programme effectue un saut à la ligne 50. Arrivé à la ligne 60, l'instruction RETURN permet de réaffecter la valeur archivée dans le compteur ordinal et donc de revenir à la position d'appel du sous-programme.
Fonctionnement
Ces structure de contrôles nécessitent donc un automate à pile pour enregistrer l'adresse de retour du sous-programme.
Repose sur la notion de pile, avec deux instructions :
- CALL [X]: empile la valeur Y du registre PC et effectue un saut à l'adresse X.
- RETURN : dépile la valeur Y et effectue un saut à cette adresse.
Procédures
Aujourd'hui les sous-programmes sont utilisés pour améliorer la structuration d'un programme. Repérés par une étiquette de branchement, ils peuvent donc être considérés comme une extension du saut inconditionnel et, du point de vue de la programmation structurée, en partagent la plupart des défauts.
Beaucoup de langages modernes ne supportent donc pas directement la notion de sous-programme au profit de constructions de haut niveau qui peuvent être appelées, d'un langage à l'autre procédure, fonction, méthode, ou routine par exemple. Ces constructions ajoutent la notion de passage de paramètres et surtout le cloisonnement des espaces de nom pour éviter que le sous-programme ait un effet de bord sur la routine appelante.
Extensions
Il existe diverses extensions à la notion de procédure comme les coroutines, signaux et slots, fonctions de rappel(callback), méthodes virtuelles...
Elles permettent de modifier dynamiquement, c’est-à-dire à l'exécution, la structure du flot d'exécution du programme.
Exceptions
Tout programme en exécution peut être sujet à des erreurs pour lesquels des stratégies de détection et de réparation sont possibles. Ces erreurs ne sont pas des bugs mais des conditions particulières (ou conditions exceptionnelles, ou exceptions) dans le déroulement normal d'une partie d'un programme.
Par exemple, l'absence d'un fichier utile n'est pas un bug du programme ; par contre, ne pas gérer son absence en serait un.
Quand on n'a pas d'outil pour séparer l'exécution normale et l'exécution exceptionnelle du programme, un algorithme, dont l'exécution normale s'exprime de façon simple et élégante, peut devenir difficile à maintenir une fois « enrobé » par une logique de traitement de ces situations exceptionnelles ; disposer au niveau du langage de programmation de structures pour différencier l'exécution normale de l'exécution dans un contexte exceptionnel peut être utile. Ces structures de contrôles forment un système de gestion d'exceptions.
Exemples d'implémentations :
- Python : try : Instruction except Exception: Handler
Programmation multitâche
Dans un système multitâche, plusieurs flots d'exécutions, appelés processus légers, s'exécutent simultanément.
Il est alors nécessaire d'assurer la synchronisation de ces flots d'exécution. Dans la plupart des langages, c'est fait par des bibliothèques externes, certains d'entre eux intègrent néanmoins des structures de contrôle permettent d'agir sur des taches concourantes.
Exemples d'implémentations:
- Ada : accept Rendezvous do Instruction end Rendezvous;
Programmation événementielle
La programmation événementielle est une autre façon de contrôler le flot d'exécutions d'un programme. Il s'agit de créer des gestionnaires qui viendront s'abonner à une boucle mère, chargée d'aiguiller les évènements qui affectent le logiciel.
Cette méthodologie a été popularisée par les bibliothèque de programmation des interfaces utilisateurs. Elle est en effet particulièrement adaptée pour gérer les mouvements de souris par exemple. On retrouve le même principe dans des outils non graphiques comme awk ou XSLT par exemple. A un niveau plus proche du matériel on retrouve une philosophie similaire avec les interruptions.
Annexes
Articles connexes
Wikimedia Foundation. 2010.