Cours Langage C Complet ⚙️
Les 12 chapitres essentiels — de la syntaxe de base aux pointeurs et à la gestion mémoire, avec exemples et quiz
7. Les pointeurs
2. Variables et types de données
8. Allocation dynamique
3. Opérateurs et expressions
9. Les chaînes de caractères
4. Structures conditionnelles
10. Structures et unions
5. Les boucles
11. Fichiers et I/O
6. Fonctions
12. Préprocesseur et bonnes pratiques
🏠 Hub Programmation
🐍 Python
⚡ JavaScript
🔷 TypeScript
☁️ AWS Cloud Practitioner
Introduction et premier programme
int main(void)
Anatomie : #include
gcc hello.c -o hello
# Exécuter
./hello
# Compiler avec warnings (TOUJOURS recommandé)
gcc -Wall -Wextra -Werror -std=c17 hello.c -o hello
# Étapes de compilation :
# 1. Préprocesseur (#include, #define → .i)
# 2. Compilateur (code C → assembleur .s)
# 3. Assembleur (assembleur → objet .o)
# 4. Linker (objets → exécutable)
| Caractéristique | C |
|---|---|
| Typage | Statique et faible — types déclarés, mais casts implicites fréquents |
| Paradigme | Impératif, procédural (pas d'objet) |
| Compilation | Compilé directement en code machine natif |
| Mémoire | Gestion manuelle (malloc/free — pas de garbage collector) |
| Performance | Très rapide — proche du matériel |
| Usage | OS (Linux), embarqué, drivers, jeux, systèmes critiques |
Pourquoi apprendre C ? Le C est la base de presque tout : Linux, Windows, Python (interpréteur CPython), et la majorité des langages modernes dérivent de sa syntaxe. Comprendre le C, c'est comprendre comment un ordinateur fonctionne réellement.
Variables et types de données
| Type | Taille typique | Plage | Usage |
|---|---|---|---|
| char | 1 octet | -128 à 127 | Caractère ASCII / petit entier |
| short | 2 octets | -32 768 à 32 767 | Petit entier |
| int | 4 octets | ±2.1 milliards | Entier standard |
| long | 4-8 octets | Varie | Grand entier |
| long long | 8 octets | ±9.2 × 10¹⁸ | Très grand entier |
| float | 4 octets | ~7 chiffres | Flottant simple |
| double | 8 octets | ~15 chiffres | Flottant double (par défaut) |
| _Bool | 1 octet | 0 ou 1 | Booléen (C99, ou stdbool.h) |
Les tailles ne sont pas garanties ! Le standard C garantit seulement des minimums. Utilisez
#include
#include
int main(void)
double f = 3.14159;
char c = 'X';
char nom[] = « Alice »;
printf(« %d\n », n); // 42 (int)
printf(« %f\n », f); // 3.141590 (double)
printf(« %.2f\n », f); // 3.14 (2 décimales)
printf(« %c\n », c); // X (char)
printf(« %s\n », nom); // Alice (string)
printf(« %p\n », &n); // 0x7ff… (adresse)
printf(« %x\n », 255); // ff (hexadécimal)
printf(« %05d\n », 42); // 00042 (padding)
// scanf — lire une entrée
int x;
scanf(« %d », &x); // ⚠️ & obligatoire (adresse de la variable)
int varie selon la plateforme (2 ou 4 octets). int32_t (de Opérateurs et expressions
+ – * / % ++ —
// ⚠️ Division entière
7 / 2 // 3 (pas 3.5 — les deux sont int)
7.0 / 2 // 3.5 (un opérande est double)
// Comparaison
== != < > <= >=
// Logique
&& // ET (short-circuit)
|| // OU (short-circuit)
! // NON
// Bitwise (opérations bit à bit)
& // AND bit à bit
| // OR bit à bit
^ // XOR
~ // NOT (complément)
<< // Décalage gauche (×2)
>> // Décalage droite (÷2)
// Ternaire
int max = (a > b) ? a : b;
// Casting explicite
double result = (double)7 / 2; // 3.5
Piège classique : if (x = 5) est un assignment, pas une comparaison ! C'est toujours vrai. L'erreur correcte : if (x == 5). Activez -Wall pour détecter ce bug.
Structures conditionnelles
if (age < 18) else if (age < 65) else
// switch — int ou char uniquement (pas de string !)
switch (note)
Pas de booléen natif en C89. En C, 0 = false, tout le reste = true. Depuis C99,
Les boucles
for (int i = 0; i < 5; i++)
// while
int i = 0;
while (i < 5)
// do-while — exécuté AU MOINS une fois
do while (n <= 0);
// break = sortir de la boucle
// continue = passer à l'itération suivante
int nombres[5] = ;
int zeros[100] = ; // Tous à 0
char msg[] = « Hello »; // Taille déduite (6 avec '\0')
// Accès
nombres[0] // 10
nombres[4] // 50
nombres[5] // ⚠️ UNDEFINED BEHAVIOR (hors limites)
// Taille d'un tableau
int len = sizeof(nombres) / sizeof(nombres[0]); // 5
// Parcourir
for (int i = 0; i < len; i++)
// Tableau 2D
int matrice[3][3] = ;
Pas de vérification des limites ! En C, accéder à arr[10] sur un tableau de taille 5 ne produit aucune erreur à la compilation. C'est un undefined behavior — le programme peut crasher, corrompre la mémoire, ou pire : sembler fonctionner.
sizeof(arr) / sizeof(arr[0]) — sizeof donne la taille totale en octets, divisée par la taille d'un élément. Attention : ne fonctionne PAS si le tableau a été passé en paramètre (il devient un pointeur).Fonctions
// Prototype (déclaration) — avant main
int addition(int a, int b);
void saluer(const char *nom);
int main(void)
// Définition (implémentation)
int addition(int a, int b)
void saluer(const char *nom)
void doubler(int x)
int n = 5;
doubler(n);
printf(« %d », n); // 5 — pas modifié !
// Par ADRESSE (pointeur) — modifie l'original
void doubler(int *x)
int n = 5;
doubler(&n); // Passe l'adresse de n
printf(« %d », n); // 10 — modifié ✅
// Tableau en paramètre = toujours un pointeur
void remplir(int arr[], int taille)
En C, tout est passé par valeur. Même les pointeurs sont passés par valeur (la fonction reçoit une copie de l'adresse). Mais puisqu'on a l'adresse, on peut modifier la donnée pointée. Les tableaux « deviennent » un pointeur quand passés en paramètre.
&variable et en la recevant comme pointeur int *x. On accède à la valeur avec *x (déréférencement).Les pointeurs
int x = 42;
int *ptr = &x; // ptr contient l'adresse de x
printf(« %d\n », x); // 42 — la valeur
printf(« %p\n », (void*)ptr); // 0x7ff… — l'adresse
printf(« %d\n », *ptr); // 42 — valeur à l'adresse (déréférencement)
*ptr = 100; // Modifie x à travers le pointeur
printf(« %d\n », x); // 100
// Résumé des opérateurs
// &x → adresse de x
// *ptr → valeur à l'adresse contenue dans ptr
// int *p → déclaration d'un pointeur vers int
int arr[5] = ;
int *p = arr; // p pointe vers arr[0]
printf(« %d\n », *p); // 10 (arr[0])
printf(« %d\n », *(p+1)); // 20 (arr[1])
printf(« %d\n », *(p+2)); // 30 (arr[2])
// Arithmétique des pointeurs
p++; // Avance de sizeof(int) octets (pas de 1 !)
// Ces notations sont ÉQUIVALENTES :
arr[i] // ← notation tableau
*(arr + i) // ← notation pointeur
int *p = NULL;
// TOUJOURS vérifier avant de déréférencer
if (p != NULL)
// Dangling pointer — pointe vers de la mémoire libérée
int *p = malloc(sizeof(int));
free(p);
p = NULL; // ✅ Toujours mettre à NULL après free
// Pointeur de pointeur
int x = 5;
int *p = &x;
int **pp = &p; // Pointeur vers un pointeur
printf(« %d », **pp); // 5
Les pointeurs sont la source n°1 de bugs en C : segfault (déréférencement de NULL), buffer overflow (écrire hors limites), memory leak (oublier free), use-after-free (utiliser après free). Des langages comme Python ou JavaScript éliminent ces problèmes avec un garbage collector.
*p donne la valeur de x (déréférencement). Modifier *p modifie x.Allocation dynamique
// malloc — alloue des octets (non initialisés)
int *arr = malloc(5 * sizeof(int));
if (arr == NULL)
// calloc — alloue ET initialise à 0
int *arr2 = calloc(5, sizeof(int)); // 5 éléments, chacun sizeof(int)
// realloc — redimensionner
arr = realloc(arr, 10 * sizeof(int)); // Agrandit à 10 éléments
// Utiliser comme un tableau normal
arr[0] = 42;
arr[1] = 84;
// free — TOUJOURS libérer la mémoire
free(arr);
arr = NULL; // Bonne pratique
| Erreur | Cause | Conséquence |
|---|---|---|
| Memory leak | malloc sans free | La mémoire n'est jamais libérée → le programme consomme de plus en plus |
| Use-after-free | Utiliser un pointeur après free | Données corrompues, crash imprévisible |
| Double free | free() deux fois sur le même pointeur | Crash ou corruption du heap |
| Buffer overflow | Écrire au-delà de la zone allouée | Corruption mémoire, faille de sécurité |
| Null dereference | *ptr quand ptr == NULL | Segmentation fault |
Outil indispensable : Valgrind. valgrind ./mon_programme détecte les fuites mémoire, les accès invalides, et les use-after-free. Utilisez-le systématiquement pendant le développement.
Les chaînes de caractères
// String = tableau de char terminé par '\0' (null terminator)
char nom[] = « Alice »;
// En mémoire : ['A']['l']['i']['c']['e']['\0']
// Taille = 6 (5 caractères + '\0')
// Déclaration manuelle
char msg[20] = « Hello »; // Buffer de 20, contenu « Hello\0 »
// Pointeur vers string (en lecture seule !)
const char *s = « World »; // Pointe vers un littéral (non modifiable)
char s2[] = » World »;
strlen(s1); // 5 (ne compte pas '\0')
strcpy(s1, « Bonjour »); // Copie dans s1
strncpy(s1, « Hi », 49); // Copie avec limite (sûr)
strcat(s1, s2); // Concatène s2 à s1
strncat(s1, s2, 49); // Concatène avec limite
strcmp(s1, s2); // 0 si égaux, <0 ou >0 sinon
strncmp(s1, s2, 3); // Compare les 3 premiers chars
strchr(s1, 'o'); // Pointeur vers 1re occurrence de 'o'
strstr(s1, « jour »); // Pointeur vers 1re occurrence de « jour »
// Conversion
atoi(« 42 »); // 42 (string → int)
atof(« 3.14 »); // 3.14 (string → double)
sprintf(buf, « Age: %d », 25); // printf dans un buffer
snprintf(buf, 50, « Age: %d », 25); // Avec limite (sûr)
TOUJOURS les versions « n » : Utilisez strncpy, strncat, snprintf au lieu de strcpy, strcat, sprintf. Les versions sans limite sont des failles de sécurité majeures (buffer overflow).
Structures et unions
typedef struct Personne;
// Création et initialisation
Personne alice = ;
Personne bob = ; // Designated init (C99)
// Accès avec le point (.)
printf(« %s a %d ans\n », alice.nom, alice.age);
// Pointeur vers struct — accès avec la flèche (->)
Personne *ptr = &alice;
printf(« %s\n », ptr->nom); // Équivalent à (*ptr).nom
ptr->age = 26;
// Struct dynamique
Personne *p = malloc(sizeof(Personne));
strcpy(p->nom, « Charlie »);
p->age = 28;
free(p);
typedef enum Couleur;
Couleur c = VERT;
// union — un seul membre actif à la fois (partage la mémoire)
typedef union Valeur;
Valeur v;
v.entier = 42; // OK
v.flottant = 3.14; // Écrase v.entier !
// sizeof(Valeur) = taille du PLUS GRAND membre (20)
personne.nom. -> = accès via un pointeur : ptr->nom (raccourci de (*ptr).nom).Fichiers et I/O
// Ouvrir un fichier
FILE *f = fopen(« data.txt », « r »); // r=lire, w=écrire, a=ajouter
if (f == NULL)
// Lire ligne par ligne
char ligne[256];
while (fgets(ligne, sizeof(ligne), f) != NULL)
// Écrire
FILE *out = fopen(« output.txt », « w »);
fprintf(out, « Nom: %s, Age: %d\n », « Alice », 25);
fputs(« Ligne de texte\n », out);
// TOUJOURS fermer
fclose(f);
fclose(out);
| Mode | Action |
|---|---|
| « r » | Lire (fichier doit exister) |
| « w » | Écrire (crée ou écrase) |
| « a » | Ajouter à la fin |
| « r+ » | Lire et écrire (doit exister) |
| « w+ » | Lire et écrire (crée ou écrase) |
| « rb » / « wb » | Mode binaire |
Personne p = ;
FILE *f = fopen(« data.bin », « wb »);
fwrite(&p, sizeof(Personne), 1, f);
fclose(f);
FILE *g = fopen(« data.bin », « rb »);
Personne lu;
fread(&lu, sizeof(Personne), 1, g);
fclose(g);
Préprocesseur et bonnes pratiques
#include
#include « mon_header.h » // Fichier local (cherche dans le dossier courant)
// #define — constante ou macro
#define PI 3.14159
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define TAILLE_MAX 1024
// #ifdef / #ifndef — compilation conditionnelle
#ifdef DEBUG
printf(« Mode debug\n »);
#endif
// Include guard — éviter la double inclusion
#ifndef MON_HEADER_H
#define MON_HEADER_H
// … contenu du header …
#endif
// Ou pragmatiquement (non-standard mais supporté partout)
#pragma once
// projet/
// ├── src/
// │ ├── main.c
// │ ├── personne.c
// │ └── utils.c
// ├── include/
// │ ├── personne.h
// │ └── utils.h
// └── Makefile
// personne.h (header = déclarations)
#pragma once
typedef struct Personne;
Personne personne_creer(const char *nom, int age);
void personne_afficher(const Personne *p);
// personne.c (implémentation)
#include « personne.h »
#include
#include
Personne personne_creer(const char *nom, int age)
✅ À FAIRE
• -Wall -Wextra -Werror toujours
• const pour les paramètres en lecture
• strncpy/snprintf (versions sûres)
• Vérifier malloc != NULL
• free() puis ptr = NULL
• Valgrind pour les fuites mémoire
• Include guards / #pragma once
• sizeof(*ptr) dans malloc
• Séparer .h (déclaration) et .c (code)
❌ À ÉVITER
• gets() (supprimé — faille buffer overflow)
• strcpy/sprintf sans limite
• Variables non initialisées
• Caster le retour de malloc (inutile en C)
• Ignorer les warnings
• Magic numbers (utiliser #define)
• Variables globales
• goto (sauf gestion d'erreur)
• Fonctions de plus de 40 lignes
CC = gcc
CFLAGS = -Wall -Wextra -Werror -std=c17
SRC = src/main.c src/personne.c
OBJ = $(SRC:.c=.o)
TARGET = app
$(TARGET): $(OBJ)
$(CC) $(CFLAGS) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
clean:
rm -f $(OBJ) $(TARGET)
fgets().#pragma once ou #ifndef/#define/#endif.🏠 Hub Programmation
🐍 Cours Python
⚡ Cours JavaScript
🔷 Cours TypeScript
☁️ AWS Cloud Practitioner
Cours Langage C Complet — Des bases aux pointeurs et à la gestion mémoire
Référence : cppreference.com/c | man pages

