Initiation au C
Nous allons écrire de petits programmes qui écrivent dans le terminal. Le but est d’apprendre :
- à compiler un seul fichier C (compiler = transformer le programme C texte en fichier exécutable que la machine peut exécuter)
- écrire un programme C basique (sans pointeur)
⌛ Plus tard, on fera des programmes avec plusieurs fichiers, et on verra les pointeurs.
Mon premier programme C
Voici mon premier programme en C :
#include <stdio.h>
int main() {
printf("Bienvenue à l'ENS Lyon/n");
return 0;
}
Vous pouvez mettre ce contenu dans un fichier :
🖹 programme.c
#include <stdio.h>permet d’utiliser des fonctions pour écrire et lire dans la console.
int main()est le début de la définition de la fonction principalemain. Sa définition commence par le type que renvoie la fonction, ici un entierint. La fonction renvoie0quand tout s’est bien passé. Elle renvoie une valeur non nulle pour signaler une erreur.printfpermet d’écrire dans la console.return 0;quitte la fonction et renvoie0.
#include <stdio.h>
#include <stdlib.h>
int main() {
printf("Bienvenue à l'ENS Lyon/n");
return EXIT_SUCCESS;
}
Compilation
Le fichier 🖹 programme.c n’est pas exécutable : il s’agit d’un fichier texte que le processeur ne comprend pas. Pour construire un fichier exécutable par la machine, on utilise un compilateur. Ce processus s’appelle la compilation.
Pour compiler, taper :
gcc programme.c
On obtient un nouveau fichier qui est exécutable :
🖹 programme.c
⚙ a.out
et que l’on peut lancer :
./a.out
Bibliothèque standard
Le langage C est fourni avec la bibliothèque standard. Elle offre des fichiers appelés fichiers d’en-tête ou fichier header. Par exemple, le fichier stdio.h offre les fonctionnalités d’entrées/sortie sur la console. Les fichiers d’en-tête sont normalement disponibles dans le dossier usr/include. Ils contiennent des signatures de fonctions.
⌛ On peut aussi créer des fichiers header nous-même, ou alors utiliser ceux d’une bibliothèque non-standard (comme la bibliothèque raylib pour dessiner à l’écran !).
Voici les fichiers d’en-tête de la bibliothèque standard :
Bibliothèque standard C (C23)
│
├── Entrées / Sorties & Utilitaires
│ ├── <stdio.h> (E/S standard : printf, scanf, fopen…)
│ ├── <stdlib.h> (malloc, exit, atoi, rand…)
│ ├── <string.h> (strlen, strcpy, memcpy…)
│ ├── <time.h> (time, clock, struct tm…)
│ ├── <locale.h> (setlocale, conventions régionales)
│ └── <uchar.h> (Unicode UTF-16/UTF-32)
│
├── Chaînes & Caractères
│ ├── <ctype.h> (isalpha, tolower…)
│ ├── <wchar.h> (wchar_t, fonctions chaînes larges)
│ └── <wctype.h> (classification/conversion wchar_t)
│
├── Mathématiques
│ ├── <math.h> (sin, cos, sqrt…)
│ ├── <complex.h> (nombres complexes)
│ ├── <tgmath.h> (maths génériques type-safe)
│ ├── <float.h> (caractéristiques flottants)
│ └── <fenv.h> (contrôle environnement flottant)
│
├── Types & Limites
│ ├── <stddef.h> (size_t, NULL, ptrdiff_t…)
│ ├── <stdbool.h> (bool, true, false)
│ ├── <stdint.h> (entiers fixes : int32_t…)
│ ├── <inttypes.h> (formatage et macros pour stdint)
│ ├── <limits.h> (INT_MAX, etc.)
│ ├── <stdalign.h> (alignement mémoire, alignof…)
│ ├── <stdnoreturn.h>(_Noreturn)
│ └── <iso646.h> (alias syntaxiques : and, or, not…)
│
├── Gestion d’erreurs & Signaux
│ ├── <assert.h> (assert)
│ ├── <errno.h> (errno, codes d’erreur)
│ ├── <signal.h> (SIGINT, SIGSEGV…)
│ └── <setjmp.h> (setjmp/longjmp)
│
├── Fonctions variadiques
│ └── <stdarg.h> (va_list, va_start, va_arg…)
│
└── Concurrence (C11+)
├── <threads.h> (threads, mutex, cond vars)
└── <stdatomic.h> (atomiques)
Entrées-sorties
stdio.h est le fichier pour de la bibliothèque standard (std) pour les entrées-sorties (io).
Pour utiliser ces fonctions, on écrit :
#include <stdio.h>
Ecrire sur stdout (sortie standard)
La sortie standard est typiquement la console, mais cela peut être un fichier, ou alors une entrée à un autre programme.
putchar('c') écrit le caractère c dans le terminal.
printf est la fonction pour écrire d’un coup des informations plus compliquées ✎ :
printf("Bonjour");
printf("Bonjour\n");
printf("Voici votre identifiant : %d", id);
printf("Voici votre nouveau prénom : %s", prenom);
printf("Voici votre nouveau prénom et votre identifiant : %s %d", prenom, id);
⚠ Ne jamais écrire printf(dataFromTheUser) car l’utilisateur pourrait écrire des %s, %d, voire des %n qui écrivent dans la mémoire. Le comportement peut être inattendu, et c’est faille de sécurité garantie :).
Lire l’entrée stdin (entrée standard)
L’entrée standard est typiquement ce qu’écrit l’utilisateur dans la console, mais cela peut être un fichier, ou alors une sortie d’un autre programme.
La fonction getchar() renvoie le prochain caractère sur l’entrée puis le consomme, c’est-à-dire que la prochaine fois que l’on exécute getchar() on lira le prochain caractère. getchar() renvoie EOF (end of file) si c’est fini.
scanf est la fonction pour lire des informations plus compliquées que l’utilisateur écrit dans la console ou de manière générale dans stdin ⌨ :
int n;
scanf("%d", &n);
On peut aussi lire plusieurs variables d’un coup.
int n, m;
scanf("%d", &n, &m);
La fonction scanf renvoie le nombre de variables qui ont été affectées correctement.
int n, m;
int nbLus = scanf("%d %d", &n, &m);
Spécification de formats
| Spécificateur | Type attendu | Exemple | Résultat affiché |
|---|---|---|---|
%d ou %i | int (entier signé) | printf("%d", -42); | -42 |
%u | unsigned int | printf("%u", 42); | 42 |
%o | entier non signé (octal) | printf("%o", 42); | 52 |
%x | entier non signé (hexadécimal min.) | printf("%x", 42); | 2a |
%X | entier non signé (hexadécimal maj.) | printf("%X", 42); | 2A |
%c | char (caractère) | printf("%c", 'A'); | A |
%s | chaîne (char*) | printf("%s", "Hello"); | Hello |
%f | float ou double (décimal) | printf("%f", 3.14); | 3.140000 |
%.2f | float ou double (précision) | printf("%.2f", 3.14); | 3.14 |
%e / %E | float ou double (scientifique) | printf("%e", 1234.0); | 1.234000e+03 |
%g / %G | float ou double (auto %f ou %e) | printf("%g", 1234.0); | 1234 |
%p | pointeur (void*) | printf("%p", ptr); | 0x7ffee2bff6c0 |
%% | affiche % | printf("50%%"); | 50% |
Quelques éléments du langage C
Une déclaration de variable est un morceau de code qui renseigne le type d’une variable. Par exemple :
int x;
Il y a pleins de types primitifs : int, long, unsigned int, char, bool (avec #include <stdbool.h>).
Une instruction est un morceau du programme qui ne renvoie rien. Par exemple, printf("aïe");, if(x == 0) y = 1;
Une expression est un morceau du programme qui renvoie une valeur. Par exemple 1+2. Mais aussi x = 42 qui renvoie… 42. Ou printf("aïe") (sans le point-virgule) qui renvoie le nombre de caractères écrits à l’écran.
Incrémentation
Considérons l’instruction :
x = 42;
- Both expressions
x++and++xincrementx. - The value of the expression
x++is 42. - The value of expression
++xis 43.
L’instruction (qui est aussi une expression) suivante
x += 3;
ajoute 3 à x.
Boucle for
for(int i = 0; i < n; i++)
...
for(int i = 0; i < n; i++) {
...
}
En fait, la boucle for en C est puissante. Elle a l’expressivité des boucles while :
for(expr1; expr2; expr3) {
...
}
est équivalent à
expr1;
while(expr2) {
...
expr3;
}
Au contraire, dans les langages comme Pascal, la boucle for est for i := 1 to n, ou for i := ... to ... step .... Le fragment de Pascal avec que des boucles for et pas d’appels récursifs correspond à la classe des fonctions récursives primitives (cf. cours de FDI). Un programme dans ce fragment termine toujours. Ce fragment n’est pas Turing-complet. Si on ajoute les boucles `while’, on obtient la classe des fonctions récursives, i.e. Pascal dans son intégralité est Turing-complet.
La fonction main
Un programme C contient toujours une fonction main qui est la fonction appelée quand on exécute le programme :
#include <stdlib.h>
int main() {
...
return EXIT_SUCCESS;
}
On verra plus tard une signature plus compliquée pour main car on peut gérer des arguments donnés au programme.
Utilisation du compilateur
gcc -Wall -pedantic -ainsi -std=c99 programme.c -o programme
| Options | Meaning |
|---|---|
| -Wall | enables all compiler’s warning messages |
| -Wextra | still a bit more warnings |
| -pedantic | enables all compiler’s warning messages |
| -ansi | turns off gcc features that are not in the ANSI C |
| -std=c99 | ANSI C99 |
| -std=c11 | ANSI C99 |
| -o programme | tells that the output is the file programme |
Variables
La portée (scope) d’une variable est la portion de code dans laquelle la variable peut être utilisée.
La durée de vie (life-time) d’une variable est la durée d’existence d’une variable.
| Scope | Life-time | |
|---|---|---|
| Global variables | All | Always |
| Variables in functions | Function | Function |
| Static variables in functions | Function | Always |
Les variables statiques peuvent servir pour :
- faire un compteur et cacher le contenu du compteur en dehors
- faire une fonction pour le drag drop avec la souris (on stocke/cache la position précédente de la souris)
Mémoire
La mémoire allouée pour un programme est divisée en trois parties :
- Segment de données. On y trouve les variables globales et les variables statiques.
- Pile d’appels. On y trouve les variables locales à une fonction. Les tableaux locaux sont stockées sur la pile aussi.
- Le tas mémoire. C’est pour y mettre des données dont on ne connait pas à l’avance la taille, ou dont la taille peut modifiée au cours du programme (structure de données comme des tableaux redimensionnables, des listes chaînées, etc.). Il faut allouer la mémoire pour pouvoir l’utiliser.
Exercices
Feuille de TP.
Le C est de bas niveau…
Voici un site où on peut compiler du code C et voir le résultat en assembleur :
https://godbolt.org/
Prochaine séance
Nous allons utiliser la bibliothèque raylib pour pouvoir afficher des dessins (rectangles, points, lignes, etc.) à l’écran :).
- Jeu vidéo comme
agar.io - Afficher une fractale comme celle de mandelbrot
- Promenade aléatoire
- Appartenance d’un point à un polygone ?
- parcours de Graham
Pour aller plus loin
- https://emscripten.org/ Pour développer des programmes qui tournent sur le Web
- https://cs50.readthedocs.io/style/c/
- https://www.gnu.org/prep/standards/html_node/Writing-C.html#Writing-C
- Fonctions de base en langage C sur wikiversité
- Du matériel sur la génération de labyrinthes