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.).