Boucles
La où les structures de controle de type if
et switch
permettent de sauter en aval (vars le “bas”) du programme
et d’ignorer des pans du code source, il existe des
structures de contrôle qui permettent de sauter en amont (vers
le “haut”) ce qui a pour effet de répéter les instructions
déjà effectuées.
Sauter ainsi vers le haut permet d’effectuer en écrivant peu de lignes de code des comportements répétitifs.
Boucle naturelle
Une boucle dans un programme est une construction de ce type
#include <stdio.h>
int main()
{
int c = 10;
debut:
if(c-- > 0) {
printf("Tour nº %d\n", c);
goto debut;
}
}
While
Pour eviter d’avoir à creer un label de goto et un goto, on peut utiliser l’instruction while qui fonctionne de la même façon que le code précédent
#include <stdio.h>
int main()
{
int c = 10;
while(c-- > 0) {
printf("Tour nº %d\n", c);
}
}
Cela permet de faire l’économie de trois lignes et exprime plus clairement l’intention du code.
Do/while
L’instruction do/while
exécute également des instructions
en boucle, mais le test est effectué après +
avoir exécuté le bloc au moins une fois.
Ca peut être utile si on veut effectuer quelque chose en boucle peu importe la condition, mais avoir la possibilité de ne pas boucler.
Par exemple une interface qui affiche une flèche de selection et reagit aux touches “haut”/“bas” et boucle tant que l’utilisateur n’a pas appuyé sur “entrée”.
Les fonctions utilisées sont fictives.
#include <stdio.h>
int main()
{
int y = 1;
int kb = 0;
do {
print_xy("=>", 2, y); // affiche une fleche à deux colonnes et à la ligne 1
while(kb_hit() != 0); // s'assure que l'utilisateur n'appuye sur aucune touche
// ici l'utilisateur a appuyé sur une touche
} while(kb != KB_ENTER);
}
Attention, il faut un point-virgule après le
while
du do/while
.
continue et break
Deux instructions sont auxilliaires aux boucles :
continue
et break
.
continue
permet de sauter à l’endroit où
le test de boucle s’effectuer. Dans une boucle
while
ou for
il s’agit du haut de la boucle,
pour une instruction do/while
le programme
saute en bas de la boucle.
break
permet de sortir du bloc d’instructions
commandé par la actuelle. Il n’est pas possible
de sortir de plusieus boucles imbriquées en une
seule instruction sans utiliser de goto
.
De même pour continue
on ne peut répéter que
la boucle où l’on se trouve immédiatement, sans
pouvoir sortir de plusieurs bloucles imbriquées.
break
sert également à sortir d’une instruction
switch
.
For
l’instruction for
est taillée
pour exécuter un bloc un nombre déterminé de fois.
for(
*creation d'une variable de controle* ;
*test à vérifier pour rester dans la boucle* ;
*instruction à exécuter à chaque fin de boucle*
) *instructions à exécuter tant que le test est valide*
Par exemple si on veut parcourir toutes les cases d’un tableau, il est plus court d’écrire
#include <stdio.h>
int main()
{
int valeurs[] = {1, 2, 3, 4};
int somme = 0;
for (int i = 0; i < 4; i++) {
somme += valeurs[i];
}
printf("La somme des valeurs du tableau est %d.\n", somme);
}
Affichage possible
La somme des valeurs du tableau est 10.
L’équivalent avec un while d’un tel programme est
#include <stdio.h>
int main()
{
int valeurs[] = {1, 2, 3, 4};
int somme = 0;
{ // on crée une portée locale pour i
int i = 0;
do {
if ( i < 4)
break;
somme += valeurs[i];
} while(i++ || 1); // on s'assure
// que le test soit toujours valide,
// mais exécutée dans tous les cas,
// même en cas d'instruction "continue"
}
printf("La somme des valeurs du tableau est %d.\n", somme);
}
On voit ici qu’une instruction for permet dans le cas d’un parcours de boucle de faire l’économie d’environ 5 lignes de code.
Performance et “parallélisation”
Comme toute structure de contrôle qui perturbe le flux
normal de l’exécution d’un programme, utiliser
une boucle peut avoir des effets négatifs sur la performance
et un compilateur essayera généralement d’applatir le
code source d’une boucle en “déroulant la boucle”, à moins
qu’un
flag de compilation
demandant de produire
un programme le plus petit possible est défini (-Osmall
).
C’est à dire que le programme
int main() {
char alph[] = {'a', 'b', 'c'};
for (int i = 0; i < 3 ; i++) {
putchar(alph[i]);
}
}
Une fois compilé pourrait ressembler à
int main() {
char alph[] = {'a', 'b', 'c'};
putchar(alph[0]);
putchar(alph[1]);
putchar(alph[2]);
}
Ce qui n’aura aucune incidence sur le comportement visible du programme tout en améliorant grandement sa performance.
Les problèmes de performance liées au boucles sont addressés de front dans les langage de shader en limitant leur grammaire pour garantir qu’une boucle pourra toujours être applatie (ou “déroulée”), notamment on ne peut pas accéder à la valeur d’un tableau via une adresse inconnue lors de la compilation, de modifier la valeur d’index au sein de la boucle ou modifier l’expression d’incrément de la boucle.
Vu qu’une carte graphique possède de nombreux registres et de coeurs, chaque traitement de boucle pourra être distribué (parralélisé).
Si on écrit du code pour le CPU (en langage C et non en shader donc) ces considérations valent la peine d’être connues et il vaut mieux en règle général garder les “boucles” de son code simples en sachant reconnaître ce qui peut empêcher un compilateur d’applatir une boucle (modifier la valeur de i au sein de la boucle, utiliser une valeur d’incrément non constante, écrire des corps de boucles trop volumineux, etc.).