Langage C - Module 02 : Tutoriel, Découverte du C par la pratique


Paul Schuhmacher

Septembre 2026

Module: Langage C (T1)


Ce tutoriel est en partie inspiré du premier chapitre du livre The C programming language, The White Book ou K&R, écrit par les deux géants Dennis Ritchie, créateur du langage et Brian Kernighan, publié chez Prentice Hall, 1978.

Partie 1 : Anatomie d’un premier programme C #

  1. Créer un fichier main.c et y copier/coller le code source C suivant :
#include <stdio.h>

int main(){
    printf("hello, world\n");
    return 0;
}
  1. Enregistrer le fichier. Pour executer le programme, le compiler avec gcc (GNU/Linux). Cela produit un exécutable a.out ou .exe (sur Windows) :
gcc main.c
./aout
  1. Enlever les caractères “\n” et observer le résultat.
  2. Modifier le programme, supprimer des parties, des caractères et essayer de le recompiler et de l’executer pour voir. Consulter et lire les messages d’erreur retournés par le compilateur. Un message d’erreur indique toujours la ligne (et la colonne) où se trouve l’erreur. Identifier la ligne lorsque vous supprimez
    1. Une double quote : printf("hello, world\n);
    2. Le point virgule : printf("hello, world\n"). En C, toute instruction se termine par un point virgule.
  3. Que se passe-t-il si l’argument passé à printf contient "\c" ?
  4. Modifier le nom du programme crée à l’issue de la compilation avec l’option --output ou -o
gcc main.c -o myprogram
./myprogram

Partie 2 : Nouvelles idées #

Nous allons écrire un programme qui affiche une table de conversion entre degré Celsius et degrés Fahrenheit. Voici le résultat que l’on doit obtenir :

0   -17
20  -6
40  4
60  15
80  26
100 37
120 48
140 60
160 714
180 82
200 93
220 104
240 115
260 126
280 137
300 148

Noter le formatage de la sortie et l’alignement des colonnes

Formatage, types, boucles

  1. Identifier les variables de ce programme (valeurs à définir, manipuler, retenir) à partir de la sortie.

Voici un programme qui affiche cette table :

#include <stdio.h>
/*Fournit la table de conversion degrés Fahrenheit/Celsius*/
int main()
{
    int fahr, celsius;
    int lower, upper, step;

    lower = 0;
    upper = 300;
    step = 20;

    fahr = lower;

    while (fahr <= upper)
    {
        celsius = (fahr - 32) * 5 / 9;
        printf("%d\t%d\n", fahr, celsius);
        fahr = fahr + step;
    }

    return 0;
}
  1. Copier/coller le code source dans un fichier conversion.c. Compiler le programme et produire un executable appelé conversion. Étudier le programme. Essayer de le modifier, testez-le.

Il y a plusieurs problèmes avec ce programme. Nous allons les corriger.

  1. Premier problème : la sortie n’est pas très jolie car les nombres de sont pas justifiés à droite. Corriger ce problème avec la fonction printf en écrivant la première colonne sur 3 caractères et la deuxième colonne sur 6 caractères. Pour cela, utiliser le spécificateur de taille %xd où x indique le nombre de caractères sur lequel écrire un entier.
  2. Deuxième problème : comme nous utilisons des nombres entiers, les degrés Celsius ne sont pas précis. Par exemple, 0° F vaut environ -17.8°C, pas -17 °C. Pour régler ce problème, il faut utiliser des nombres à virgule flottante (décimaux). Corriger le programme en remplaçant les types entiers (int) par des types flottants (float). Quel effet a cette modification sur la sortie produite ?
  1. Corriger le formatage en remplaçant les spécificateurs de format %xd par %x.yf, où %f est le spécificateur pour les nombres flottants. Que contrôlent les paramètres x et y dans ce spécificateur ? Faire des tests pour y répondre :
printf("%f\t%f\n", fahr, celsius);
  1. Formater la colonne Celsius avec 2 chiffres après la virgule.
  1. Modifier le programme pour ajouter des en-têtes au dessus du tableau. Sortie attendue :
  Fahrenheit         Celsius
           0          -27.65
          20          -21.48
          40          -15.31

Noter la justification à droite des headers. Indice: compter le nombre de caractères dans chaque header et ajustez le nombre de caractères sur lequel imprimer chaque valeur de température.

  1. Obtenir la sortie suivante avec les spécificateurs %f uniquement (retirer le tab \t) :
  0  -17.8
 20   -6.7
 40    4.4
 60   15.6
 80   26.7
100   37.8
120   48.9
140   60.0
160   71.1
  1. Remplacer la boucle while par une boucle for. Voici la syntaxe de la boucle for en C :
for ([expression1]; [expression2]; [expression3])
    {
        //Instructions
    }

où :

  1. Construire la table de conversion dans l’ordre inverse, de 300°F à 0°F.
  2. Construire la table de conversion inverse (de Celsius à Fahrenheit), de -50°C à 120°C inclus, de 10 en 10. Si l’on inverse la relation, on a Tfahr=32+95Tcel\text{T}_\text{fahr} = 32 + \frac{9}{5} \text{T}_{\text{cel}}

Constantes symboliques

  1. Existe-t-il des valeurs constantes dans le programme ? Si oui, lesquelles ?
  2. Remplacer les variables à valeur constante du programme par des constantes symboliques. Une constante symbolique se définit ainsi :
#define NAME replacement list

Cette instruction spéciale, démarrant avec un # est une instruction pour le préprocesseur. Le préprocesseur est un programme qui traite le code source avant la phase de compilation. Il effectue des manipulations textuelles simples sur le fichier source, sans vérifier la syntaxe C ni le sens du code. Le préprocesseur va remplacer chaque occurrence de NAME par sa valeur de remplacement (search and replace de texte !). Par exemple :

#include <stdio.h>

//Déclaration d'une constante symbolique MAX
#define MAX 100

int main(){

    printf("MAX = %d\n", MAX);

    return 0;
}

Par convention, on écrit les constantes symboliques en majuscules pour les distinguer des noms de variables écrites usuellement en minuscules.

Le code après passage du préprocesseur et avant la compilation va ressembler à cela :

#include <stdio.h>

int main(){

    printf("MAX = %d\n", 100);

    return 0;
}

L’exécution du programme produit alors la sortie suivante :

MAX = 100

C’est une mauvaise pratique de “hard coder” des valeurs dans le programme (nombres “magiques”), car ces valeurs, esseulées, ne donnent aucune information à une personne relisant le programme (que signifient-elles ? quel contexte ?). Et s’il on souhaite les changer, il faut penser à le faire partout dans le code. Une solution est de donner un nom à ces nombres, via les constantes symboliques.

Introduction aux entrées/sorties d’un programme C

Le programme perroquet: getchar et putchar

  1. Créer un nouveau fichier parrot.c. Ce programme va copier sur la sortie les caractères qu’il reçoit en entrée tant qu’il ne reçoit pas d’instruction de terminer la saisie. Voici le code C de ce programme :
#include <stdio.h>

int main()
{
    int c;
    while ((c = getchar()) != EOF)
    {
        putchar(c);
    }
    return 0;
}

Compiler, exécuter et tester le code.

Ce programme répète tout ce que vous écrivez.

Analyse du code :

Sur le fonctionnement du terminal et du flux stdin

En C, la fonction getchar() sert à lire un seul caractère sur le flux d’entrée standard (stdin, le clavier par défaut). Elle renvoie le code ASCII du caractère lu (promu en int) ou la constante EOF (End Of File, valant généralement -1) en cas d’erreur ou de fin de fichier.

Ces informations se trouvent dans la documentation de la librairie standard, accessible via le programme Unix de manuel man :

# Accéder au manuel de getchar
man getchar

getchar() ne lit pas la touche immédiatement au moment où l’on appuie dessus. Le terminal utilise un tampon de ligne (line-buffered) par défaut.

  1. Les caractères tapés au clavier sont d’abord accumulés dans une mémoire tampon du terminal. Rien n’est transmis au programme tant que l’on n’appuie pas sur Entrée (ce qui permet de corriger sa saisie avec la touche Effacer par exemple !).
  2. Lorsque l’on appuie sur la touche Entrée le terminal vide son buffer (flush) et envoie alors l’intégralité de la ligne (et le caractère de saut de ligne "\n" !) au stream stdin.
  3. Le premier appel à getchar() extrait et consomme le premier caractère disponible dans le flux stdin. Les caractères suivants y restent stockés en attente des prochains appels à getchar(). Lorsque le tampon de stdin reçoit le signal fin de saisie (0 byte lu) via Ctl + D, cela indique une fin de flux (il n’y a plus rien à lire !). getchar retourne alors EOF.

Voici un schéma récapitulant ce qu’il se passe à l’utilisation du programme :

[ SOURCE ]            [ BUFFER du TERMINAL ]        [ BUFFER du stream STDIN ]             [ PROGRAMME C ]
 
+---------+           +---------------------+       +---------------------------+          +-------------+
| Clavier | --[1]-->  | 'h','e','l','l','o' |       |                           |          |             |
+---------+           +---------------------+       |                           |          |             |
                           |                        |                           |          |             |
                           | [2] Touche "Entrée"    |                           |          |             |
                           v                        |                           |          |             |
                      +------------------+          |                           |          |             |
                      | (Flush)          | --[3]--> | 'h','e','l','l','o' '\n'  |          |             |
                      +------------------+          +---------------------------+          |             |
                                                      ^                                    |             |
                                                      | Curseur de lecture                 |             |
                                                      |                                    |             |
                                                      +-----[4] getchar() ---------------->| c =  'h'    |         
                                                                                           |             |
                                                      |     [5] (Avance le curseur)        |             |
                                                      v                                    |             |
                                                    +---------------------------+          |             |
                                                    | 'e','l','l','o' '\n'      |          |             |
                                                    +---------------------------+          +-------------+

Lorsque l’on appuie sur Ctrl + D, le terminal transmet immédiatement le contenu de son tampon à stdin :

Qu’est ce qu’un flux (stream)?

En C, un flux ou stream est une abstraction logicielle qui représente une source ou une destination séquentielle de données (bytes ou octets). Il sert d’intermédiaire entre le programme C et les périphériques physiques (clavier, écran, fichier sur disque, socket réseau).

Un stream est caractérisé par :

L’intérêt principal d’un flux est qu’il n’est pas nécessaire de charger l’intégralité des données en mémoire pour les traiter. Les données sont lues, transformées et transmises au fur et à mesure. Cela permet de traiter des flux de données en continu (data streams) qui n’ont pas de taille définie à l’avance ou qui ne s’arrêtent jamais : la saisie clavier d’un utilisateur, les logs d’un serveur en temps réel ou un flux réseau (ex: streaming vidéo).

Par exemple, un programme C qui traite un fichier de 100 Go avec getchar() consomme exactement la même quantité de mémoire RAM (quelques octets pour le tampon) que s’il traitait un fichier de 10 octets !

                      Flux stdin                                   Source
                        [FLAGS]
            Curseur
                |
                v
              ---------------------------------------
getchar() <---['h']['e']['l']['l']['o']['\n'][][][][]   <----      clavier (par défaut)
              ---------------------------------------

Un flux est comme un tuyau : c’est un canal dans lequel les données s’écoulent en file indienne (séquentiellement) : chaque donnée arrive l’une après l’autre. Le tuyau peut stocker une petite quantité de données (buffer). Ce tuyau est connecté à une source de données. Le flux stdin est branché par défaut sur le clavier. Il est consommé par getchar(), une donnée après l’autre. Quand le tuyau est vide, getchar() le constate et renvoie EOF.

Redirection(s) de flux

Un flux permet de créer une abstraction sur l’origine des données. Il est possible de rediriger les flux c’est à dire de l’associer à une autre source ou destination. Par exemple, le flux stdin est associé aux saisies clavier par défaut. On peut rediriger stdin : au lieu d’être associé au clavier, on peut l’associer à un fichier !

stdin n’est rien d’autre qu’un canal ouvert automatiquement et accessible à chaque programme. Ce qui se trouve à l’autre bout du canal (source des données) peut changer !

Créer le fichier shakespeare.txt contenant le sonnet suivant :

From fairest creatures we desire increase,
That thereby beauty's rose might never die,
But as the riper should by time decease,
His tender heir might bear his memory:
But thou, contracted to thine own bright eyes,
Feed'st thy light's flame with self-substantial fuel,
Making a famine where abundance lies,
Thy self thy foe, to thy sweet self too cruel:
Thou that art now the world's fresh ornament,
And only herald to the gaudy spring,
Within thine own bud buriest thy content,
And tender churl mak'st waste in niggarding:
Pity the world, or else this glutton be,
To eat the world's due, by the grave and thee.

À l’exécution d’un programme, il est possible de rediriger le flux d’entrée standard avec le symbole <. Par exemple :

cc parrot.c -o parrot
./parrot < shakespeare.txt

Ici on dit “Associe le fichier shakespeare.txt au flux d’entrée standard” (et non le clavier par défaut!). Le programme fonctionne de la même manière et va lire le contenu du fichier ! getchar() lit un flux séquentiellement d’octets (bytes) sans se soucier de la source de données !

                               Flux stdin                             Source
            Curseur
                |                                          ------`shakespeare.txt`
                v                                          |
              ---------------------------------------      |
getchar() <---['F']['r']['o']['m'][' ']['f'][..][][][]  <---      clavier (défaut)
              ---------------------------------------

Remarquez comment les abstractions fournies par le système Unix et le C permettent d’écrire un code totalement indépendant de la source des données. La fonction getchar() n’a pas besoin de savoir si les caractères proviennent du clavier, d’un fichier texte ou d’un réseau : elle lit simplement un flux d’octets.

On vient de rediriger le flux d’entrée standard (stdin) mais on peut également rediriger le flux de sortie standard (stdout) ! Par défaut, ce flux a pour destination le terminal. C’est le flux qu’utilise printf. On peut le rediriger vers un fichier avec le symbole >. Ainsi :

./parrot < shakespeare.txt > copy

dit “Associe le fichier copy au flux de sortie standard stdout”. Le résultat est la copie le contenu du fichier shakespeare.txt dans le fichier copy. Inspecter le contenu de copy :

cat copy

Avec les redirections de flux Unix, notre programme parrot, qui semblait un peu inutile, permet à présent de dupliquer un fichier !

Nous verrons plus tard comment rediriger les flux directement en C.

Priorité des opérateurs et ordre évaluation

  1. Retirer les parenthèses autour de c = getchar() :
#include <stdio.h>

int main()
{
    int c;
    /*while ((c = getchar()) != EOF)*/
    while (c = getchar() != EOF)
    {
        putchar(c);
    }
    return 0;
}

Dans l’expression c = getchar() != EOF, l’ordre d’évaluation dépend de la priorité, appelée précédence, des opérateurs impliqués. Chaque opérateur a une précédence différente par rapport aux autres. L’opérateur de comparaison != a une priorité supérieure à l’opérateur d’affectation =. Aussi, l’expression c = getchar() != EOF est évaluée comme suit c = (getchar() != EOF). Les parenthèses forcent l’ordre d’évaluation, ce qui est entre parenthèses est évalué d’abord.

  1. Est-ce que le programme fonctionne toujours correctement ? Remplacer putchar(c) par printf("%d", c). Expliquer le résultat obtenu.

En C, une valeur vraie (true) est représentée par une valeur entière différente de 0 (1, 2, etc.), une valeur fausse (false) est représentée exclusivement par la valeur 0.

Corriger l’erreur en remettant les parenthèses autour de c = getchar(). Vérifier le bon comportement du programme.

Des programmes plus utiles

  1. Voici un autre programme qui compte le nombre de caractères envoyés au programme :
#include <stdio.h>
int main()
{
    int nc = 0;

    while (getchar() != EOF)
        nc++;

    printf("Nombre de caractères : %d\n", nc);
    return 0;
}
  1. Réécrire le programme en utilisant une boucle for à la place de while et un double à la place d’un int pour la variable nc.
  2. Écrire un programme qui compte le nombre de lignes du texte fourni en entrée via le clavier. La caractère de retour à la ligne est '\n'.
  3. Écrire un programme qui compte le nombre de e fourni en entrée via le clavier.
  4. Utiliser le programme écrit précédemment et la redirection de flux pour compter le nombre de e présent dans le sonnet présent dans le fichier shakespeare.txt crée précédemment.

Utiliser le module mathématiques de la bibliothèque standard

Dans un nouveau fichier math.c, écrire un programme qui demande à l’utilisateur de fournir un nombre et lui affiche la racine carrée de de ce nombre. Pour calculer la racine carrée, vous pouvez utiliser la fonction sqrt(x) fournie par la standard library.

Pour accéder à cette fonction, inclure le header math.h avec #include <math.h> et compiler avec l’option -lm : gcc math.c -lm.

#include <math.h>
#include <stdio.h>

int main(){
    float x = sqrt(2);
    printf("%f\n", x);
}

et compiler ainsi :

gcc math.c -lm

L’édition des liens vers le code des fonctions mathématiques (comme sqrt) définies dans le header math.h n’est pas fait automatiquement par le compilateur (contrairement à stdio.h et stdlib.h). Pour compiler, vous devez explicitement faire l’édition des liens avec l’option -lm. Nous y reviendrons plus tard.

Aller plus loin (autonomie) #

Si vous avez terminé ou êtes en avance, continuer votre découverte du C en consultant les ressources suivantes :