Table of Contents
Introduction
Les pointeurs constituent l’une des caracteristiques les plus puissantes et les plus distinctives du langage C. Ils representent un concept fondamental qui distingue C des langages de plus haut niveau et offrent un controle direct sur la memoire de l’ordinateur.
Comprendre les pointeurs est essentiel pour tout programmeur C serieux. Ils permettent de :
- Manipuler directement la memoire : Acceder et modifier des donnees a n’importe quelle adresse memoire
- Creer des structures de donnees dynamiques : Listes chainees, arbres, graphes
- Optimiser les performances : Eviter les copies couteuses de donnees
- Implementer des fonctions generiques : Fonctions pouvant travailler avec differents types de donnees
- Interfacer avec le materiel : Acces direct aux peripheriques et registres
Malgre leur puissance, les pointeurs sont souvent consideres comme difficiles a maitriser. De nombreux bugs critiques en C proviennent d’une mauvaise utilisation des pointeurs : segmentation faults, fuites memoire, corruption de donnees. Cet article vous guidera a travers tous les aspects des pointeurs, des concepts de base aux techniques avancees.
L’objectif est de vous donner une comprehension solide qui vous permettra d’utiliser les pointeurs avec confiance et securite dans vos projets C.
Concepts Fondamentaux
Qu’est-ce qu’un Pointeur ?
Un pointeur est une variable qui stocke l’adresse memoire d’une autre variable. Plutot que de contenir directement une valeur comme un entier ou un caractere, un pointeur contient l’emplacement en memoire ou cette valeur est stockee.
Chaque variable en C occupe un espace en memoire, et cet espace possede une adresse unique. Un pointeur permet de referencer cette adresse.
int nombre = 42; // nombre est stocke quelque part en memoire
int *ptr = &nombre; // ptr contient l'adresse de nombre
printf("Valeur de nombre: %d\n", nombre); // Affiche: 42
printf("Adresse de nombre: %p\n", (void*)&nombre); // Affiche l'adresse
printf("Valeur de ptr: %p\n", (void*)ptr); // Meme adresse
printf("Valeur pointee par ptr: %d\n", *ptr); // Affiche: 42
Declaration et Initialisation
La syntaxe de declaration d’un pointeur utilise l’asterisque * pour indiquer que la variable est un pointeur vers un type specifique :
// Declaration de pointeurs
int *ptr_int; // Pointeur vers un int
char *ptr_char; // Pointeur vers un char
float *ptr_float; // Pointeur vers un float
double *ptr_double; // Pointeur vers un double
// Declaration de plusieurs pointeurs sur une ligne
int *p1, *p2, *p3; // Trois pointeurs vers int
int *p4, val; // ATTENTION: p4 est un pointeur, val est un int !
// Initialisation immediate
int x = 10;
int *px = &x; // Initialisation avec l'adresse de x
// Initialisation a NULL (bonne pratique)
int *ptr_safe = NULL;
Attention : L’asterisque s’attache au nom de la variable, pas au type. C’est pourquoi int *p1, *p2; declare deux pointeurs, mais int *p1, p2; declare un pointeur et un entier.
Les Operateurs & et *
Deux operateurs sont essentiels pour travailler avec les pointeurs :
L’operateur & (adresse de) : Retourne l’adresse memoire d’une variable.
int valeur = 100;
int *ptr = &valeur; // ptr recoit l'adresse de valeur
// Affichage de l'adresse
printf("L'adresse de valeur est: %p\n", (void*)&valeur);
L’operateur * (dereferencement) : Accede a la valeur stockee a l’adresse pointee.
int valeur = 100;
int *ptr = &valeur;
// Lecture via le pointeur
printf("Valeur pointee: %d\n", *ptr); // Affiche: 100
// Modification via le pointeur
*ptr = 200;
printf("Nouvelle valeur: %d\n", valeur); // Affiche: 200
Ces deux operateurs sont inverses l’un de l’autre :
int x = 42;
int *p = &x;
// Ces assertions sont toujours vraies
assert(*&x == x); // Dereferencer l'adresse de x donne x
assert(&*p == p); // L'adresse de la valeur pointee par p est p
assert(*p == x); // La valeur pointee par p est x
Le Pointeur NULL
NULL est une constante speciale qui represente un pointeur qui ne pointe vers aucune adresse valide. C’est une bonne pratique d’initialiser les pointeurs a NULL quand ils ne pointent pas encore vers quelque chose.
#include <stdio.h>
#include <stdlib.h>
int *ptr = NULL; // Pointeur initialise a NULL
// Verification avant utilisation
if (ptr != NULL) {
printf("Valeur: %d\n", *ptr);
} else {
printf("Le pointeur est NULL\n");
}
// Allocation memoire avec verification
ptr = malloc(sizeof(int));
if (ptr == NULL) {
fprintf(stderr, "Erreur d'allocation memoire\n");
exit(EXIT_FAILURE);
}
*ptr = 42;
printf("Valeur allouee: %d\n", *ptr);
// Liberation et remise a NULL
free(ptr);
ptr = NULL; // Evite les dangling pointers
Pourquoi utiliser NULL ?
- Permet de detecter si un pointeur est initialise
- Dereferencer NULL provoque generalement un crash immediat (plus facile a debugger qu’une corruption silencieuse)
- Facilite la verification des erreurs d’allocation
Arithmetique des Pointeurs
L’arithmetique des pointeurs est une fonctionnalite puissante de C qui permet de naviguer dans la memoire de maniere intuitive.
Increment et Decrement
Quand on incremente ou decremente un pointeur, il ne se deplace pas d’un octet mais de la taille du type pointe :
int tableau[5] = {10, 20, 30, 40, 50};
int *ptr = tableau; // ptr pointe vers tableau[0]
printf("*ptr = %d\n", *ptr); // Affiche: 10
ptr++; // Avance de sizeof(int) octets
printf("*ptr = %d\n", *ptr); // Affiche: 20
ptr++;
printf("*ptr = %d\n", *ptr); // Affiche: 30
ptr--; // Recule de sizeof(int) octets
printf("*ptr = %d\n", *ptr); // Affiche: 20
Addition et Soustraction
On peut ajouter ou soustraire un entier a un pointeur :
int tableau[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int *ptr = tableau;
// Acces direct avec arithmetique
printf("tableau[0] = %d\n", *ptr); // Affiche: 0
printf("tableau[3] = %d\n", *(ptr + 3)); // Affiche: 3
printf("tableau[7] = %d\n", *(ptr + 7)); // Affiche: 7
// Equivalent a l'indexation
printf("tableau[5] = %d\n", ptr[5]); // Affiche: 5
printf("tableau[5] = %d\n", *(ptr + 5)); // Equivalent
printf("tableau[5] = %d\n", 5[ptr]); // Aussi equivalent ! (commutativite)
Difference entre Pointeurs
La soustraction de deux pointeurs du meme type donne le nombre d’elements entre eux :
int tableau[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int *debut = &tableau[2];
int *fin = &tableau[7];
ptrdiff_t distance = fin - debut;
printf("Distance: %td elements\n", distance); // Affiche: 5
// Utile pour calculer la taille d'une portion de tableau
size_t taille = (size_t)(fin - debut);
Relation avec les Tableaux
En C, un tableau et un pointeur vers son premier element sont etroitement lies :
int tableau[5] = {10, 20, 30, 40, 50};
// Ces expressions sont equivalentes
printf("%d\n", tableau[0]); // Indexation tableau
printf("%d\n", *tableau); // Dereferencement
printf("%d\n", *(tableau + 0)); // Arithmetique pointeur
// Le nom du tableau se decompose en pointeur
int *ptr = tableau; // Pas besoin de &tableau[0]
// Ces expressions sont equivalentes
printf("%d\n", tableau[3]); // 40
printf("%d\n", ptr[3]); // 40
printf("%d\n", *(ptr + 3)); // 40
printf("%d\n", *(tableau + 3)); // 40
// ATTENTION: tableau et ptr ne sont pas identiques !
// tableau est une constante, ptr est une variable
// tableau++; // ERREUR: impossible de modifier un tableau
ptr++; // OK: ptr peut etre modifie
Parcours de Tableau avec Pointeurs
#include <stdio.h>
void afficher_tableau(int *arr, size_t taille) {
int *fin = arr + taille;
// Methode 1: avec index
for (size_t i = 0; i < taille; i++) {
printf("%d ", arr[i]);
}
printf("\n");
// Methode 2: avec pointeur et increment
for (int *p = arr; p < fin; p++) {
printf("%d ", *p);
}
printf("\n");
// Methode 3: avec arithmetique
for (size_t i = 0; i < taille; i++) {
printf("%d ", *(arr + i));
}
printf("\n");
}
int main(void) {
int nombres[] = {1, 2, 3, 4, 5};
size_t taille = sizeof(nombres) / sizeof(nombres[0]);
afficher_tableau(nombres, taille);
return 0;
}
Pointeurs de Pointeurs
Un pointeur de pointeur (ou double pointeur) est un pointeur qui stocke l’adresse d’un autre pointeur. Cette technique est utilisee dans plusieurs situations importantes.
Declaration et Utilisation de Base
int valeur = 42;
int *ptr = &valeur; // ptr pointe vers valeur
int **pptr = &ptr; // pptr pointe vers ptr
printf("valeur = %d\n", valeur); // 42
printf("*ptr = %d\n", *ptr); // 42
printf("**pptr = %d\n", **pptr); // 42
// Modification via double pointeur
**pptr = 100;
printf("valeur = %d\n", valeur); // 100
Cas d’Usage : Tableaux Dynamiques 2D
Les pointeurs de pointeurs sont couramment utilises pour creer des tableaux a deux dimensions dynamiques :
#include <stdio.h>
#include <stdlib.h>
int **creer_matrice(size_t lignes, size_t colonnes) {
// Allouer le tableau de pointeurs (lignes)
int **matrice = malloc(lignes * sizeof(int *));
if (matrice == NULL) {
return NULL;
}
// Allouer chaque ligne
for (size_t i = 0; i < lignes; i++) {
matrice[i] = malloc(colonnes * sizeof(int));
if (matrice[i] == NULL) {
// Liberer les lignes deja allouees en cas d'erreur
for (size_t j = 0; j < i; j++) {
free(matrice[j]);
}
free(matrice);
return NULL;
}
}
return matrice;
}
void liberer_matrice(int **matrice, size_t lignes) {
if (matrice == NULL) return;
for (size_t i = 0; i < lignes; i++) {
free(matrice[i]);
}
free(matrice);
}
void remplir_matrice(int **matrice, size_t lignes, size_t colonnes) {
int valeur = 1;
for (size_t i = 0; i < lignes; i++) {
for (size_t j = 0; j < colonnes; j++) {
matrice[i][j] = valeur++;
}
}
}
void afficher_matrice(int **matrice, size_t lignes, size_t colonnes) {
for (size_t i = 0; i < lignes; i++) {
for (size_t j = 0; j < colonnes; j++) {
printf("%4d ", matrice[i][j]);
}
printf("\n");
}
}
int main(void) {
size_t lignes = 3, colonnes = 4;
int **matrice = creer_matrice(lignes, colonnes);
if (matrice == NULL) {
fprintf(stderr, "Erreur d'allocation\n");
return EXIT_FAILURE;
}
remplir_matrice(matrice, lignes, colonnes);
afficher_matrice(matrice, lignes, colonnes);
liberer_matrice(matrice, lignes);
return EXIT_SUCCESS;
}
Cas d’Usage : Modification d’un Pointeur par une Fonction
Quand une fonction doit modifier un pointeur (pas juste la valeur pointee), elle a besoin d’un pointeur vers ce pointeur :
#include <stdio.h>
#include <stdlib.h>
// Cette fonction ne peut PAS modifier ptr dans main
void allouer_incorrect(int *ptr, size_t taille) {
ptr = malloc(taille * sizeof(int)); // Modifie la copie locale !
}
// Cette fonction PEUT modifier ptr dans main
void allouer_correct(int **ptr, size_t taille) {
*ptr = malloc(taille * sizeof(int)); // Modifie le pointeur original
}
// Alternative: retourner le pointeur
int *allouer_retour(size_t taille) {
return malloc(taille * sizeof(int));
}
int main(void) {
int *ptr1 = NULL;
int *ptr2 = NULL;
int *ptr3 = NULL;
// Methode incorrecte - ptr1 reste NULL
allouer_incorrect(ptr1, 10);
printf("ptr1 est %s\n", ptr1 == NULL ? "NULL" : "alloue");
// Methode correcte avec double pointeur
allouer_correct(&ptr2, 10);
printf("ptr2 est %s\n", ptr2 == NULL ? "NULL" : "alloue");
// Methode avec retour
ptr3 = allouer_retour(10);
printf("ptr3 est %s\n", ptr3 == NULL ? "NULL" : "alloue");
free(ptr2);
free(ptr3);
return 0;
}
Arguments de main : argc et argv
Le double pointeur est utilise pour argv dans la fonction main :
#include <stdio.h>
int main(int argc, char **argv) {
// argv est un tableau de chaines (tableau de pointeurs vers char)
// argc est le nombre d'arguments
printf("Nombre d'arguments: %d\n", argc);
for (int i = 0; i < argc; i++) {
printf("argv[%d] = %s\n", i, argv[i]);
}
// Equivalences
// char **argv == char *argv[]
// argv[0] == *(argv + 0)
// argv[0][0] == **argv (premier caractere du premier argument)
return 0;
}
void* et Cast
Le type void* est un pointeur generique qui peut pointer vers n’importe quel type de donnees. C’est un outil fondamental pour ecrire du code generique en C.
Caracteristiques de void*
#include <stdio.h>
int main(void) {
int entier = 42;
float flottant = 3.14f;
char caractere = 'A';
void *ptr;
// void* peut pointer vers n'importe quel type
ptr = &entier;
printf("Entier: %d\n", *(int*)ptr);
ptr = &flottant;
printf("Flottant: %.2f\n", *(float*)ptr);
ptr = &caractere;
printf("Caractere: %c\n", *(char*)ptr);
// ATTENTION: on ne peut pas dereferencer void* directement
// *ptr; // ERREUR: impossible de dereferencer void*
return 0;
}
malloc, calloc et realloc
Les fonctions d’allocation memoire retournent des void* :
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
// malloc: allocation non initialisee
int *arr1 = malloc(5 * sizeof(int));
if (arr1 == NULL) {
perror("malloc");
return EXIT_FAILURE;
}
// Contenu indefini - doit etre initialise
for (int i = 0; i < 5; i++) {
arr1[i] = i * 10;
}
// calloc: allocation initialisee a zero
int *arr2 = calloc(5, sizeof(int));
if (arr2 == NULL) {
free(arr1);
perror("calloc");
return EXIT_FAILURE;
}
// Tous les elements sont deja a 0
// realloc: redimensionnement
int *arr3 = realloc(arr1, 10 * sizeof(int));
if (arr3 == NULL) {
free(arr1); // arr1 est encore valide si realloc echoue
free(arr2);
perror("realloc");
return EXIT_FAILURE;
}
arr1 = arr3; // Mise a jour du pointeur
// Initialiser les nouveaux elements
for (int i = 5; i < 10; i++) {
arr1[i] = i * 10;
}
// Affichage
printf("arr1: ");
for (int i = 0; i < 10; i++) {
printf("%d ", arr1[i]);
}
printf("\n");
printf("arr2: ");
for (int i = 0; i < 5; i++) {
printf("%d ", arr2[i]);
}
printf("\n");
// Liberation
free(arr1);
free(arr2);
return EXIT_SUCCESS;
}
Fonctions Generiques : qsort et bsearch
Les fonctions generiques de la bibliotheque standard utilisent void* pour travailler avec n’importe quel type :
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Fonction de comparaison pour entiers (ordre croissant)
int comparer_int(const void *a, const void *b) {
int val_a = *(const int*)a;
int val_b = *(const int*)b;
if (val_a < val_b) return -1;
if (val_a > val_b) return 1;
return 0;
}
// Fonction de comparaison pour chaines
int comparer_str(const void *a, const void *b) {
// a et b sont des pointeurs vers des char*
const char *str_a = *(const char**)a;
const char *str_b = *(const char**)b;
return strcmp(str_a, str_b);
}
// Structure exemple
typedef struct {
char nom[50];
int age;
} Personne;
// Comparaison par age
int comparer_age(const void *a, const void *b) {
const Personne *p1 = (const Personne*)a;
const Personne *p2 = (const Personne*)b;
return p1->age - p2->age;
}
int main(void) {
// Tri d'entiers
int nombres[] = {64, 25, 12, 22, 11, 90, 42};
size_t n = sizeof(nombres) / sizeof(nombres[0]);
printf("Avant tri: ");
for (size_t i = 0; i < n; i++) printf("%d ", nombres[i]);
printf("\n");
qsort(nombres, n, sizeof(int), comparer_int);
printf("Apres tri: ");
for (size_t i = 0; i < n; i++) printf("%d ", nombres[i]);
printf("\n");
// Recherche binaire
int cle = 42;
int *trouve = bsearch(&cle, nombres, n, sizeof(int), comparer_int);
if (trouve != NULL) {
printf("Element %d trouve a l'index %td\n", cle, trouve - nombres);
}
// Tri de chaines
const char *mots[] = {"zebra", "apple", "mango", "banana", "cherry"};
size_t n_mots = sizeof(mots) / sizeof(mots[0]);
qsort(mots, n_mots, sizeof(char*), comparer_str);
printf("Mots tries: ");
for (size_t i = 0; i < n_mots; i++) printf("%s ", mots[i]);
printf("\n");
// Tri de structures
Personne personnes[] = {
{"Alice", 30},
{"Bob", 25},
{"Charlie", 35}
};
size_t n_pers = sizeof(personnes) / sizeof(personnes[0]);
qsort(personnes, n_pers, sizeof(Personne), comparer_age);
printf("Personnes par age:\n");
for (size_t i = 0; i < n_pers; i++) {
printf(" %s: %d ans\n", personnes[i].nom, personnes[i].age);
}
return EXIT_SUCCESS;
}
Pointeurs et Fonctions
Passage par Adresse
En C, les arguments sont passes par valeur. Pour modifier une variable de l’appelant, on passe son adresse :
#include <stdio.h>
// Passage par valeur - NE MODIFIE PAS l'original
void incrementer_valeur(int x) {
x++;
printf("Dans la fonction: x = %d\n", x);
}
// Passage par adresse - MODIFIE l'original
void incrementer_pointeur(int *x) {
(*x)++;
printf("Dans la fonction: *x = %d\n", *x);
}
// Echange de deux valeurs
void echanger(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main(void) {
int valeur = 10;
printf("Avant incrementer_valeur: %d\n", valeur);
incrementer_valeur(valeur);
printf("Apres incrementer_valeur: %d\n", valeur); // Toujours 10
printf("\n");
printf("Avant incrementer_pointeur: %d\n", valeur);
incrementer_pointeur(&valeur);
printf("Apres incrementer_pointeur: %d\n", valeur); // 11
printf("\n");
int x = 5, y = 10;
printf("Avant echange: x=%d, y=%d\n", x, y);
echanger(&x, &y);
printf("Apres echange: x=%d, y=%d\n", x, y);
return 0;
}
Pointeurs de Fonctions
Un pointeur de fonction stocke l’adresse d’une fonction, permettant d’appeler des fonctions de maniere dynamique :
#include <stdio.h>
// Fonctions mathematiques
int addition(int a, int b) { return a + b; }
int soustraction(int a, int b) { return a - b; }
int multiplication(int a, int b) { return a * b; }
int division(int a, int b) { return b != 0 ? a / b : 0; }
// Type pour un pointeur de fonction
typedef int (*operation_t)(int, int);
// Fonction qui utilise un pointeur de fonction
int calculer(int a, int b, operation_t op) {
return op(a, b);
}
// Tableau de pointeurs de fonctions
operation_t operations[] = {
addition,
soustraction,
multiplication,
division
};
const char *noms[] = {"+", "-", "*", "/"};
int main(void) {
int a = 20, b = 5;
// Declaration et utilisation basique
int (*ptr_func)(int, int);
ptr_func = addition;
printf("%d + %d = %d\n", a, b, ptr_func(a, b));
ptr_func = multiplication;
printf("%d * %d = %d\n", a, b, ptr_func(a, b));
// Avec typedef
operation_t op = soustraction;
printf("%d - %d = %d\n", a, b, op(a, b));
// Passage en parametre
printf("%d / %d = %d\n", a, b, calculer(a, b, division));
// Tableau de fonctions
printf("\nToutes les operations:\n");
for (int i = 0; i < 4; i++) {
printf("%d %s %d = %d\n", a, noms[i], b, operations[i](a, b));
}
return 0;
}
Callbacks
Les pointeurs de fonctions sont souvent utilises pour implementer des callbacks :
#include <stdio.h>
#include <stdlib.h>
// Type de callback pour traitement d'elements
typedef void (*element_callback_t)(int element, size_t index, void *contexte);
// Fonction qui parcourt un tableau et appelle un callback pour chaque element
void parcourir(int *tableau, size_t taille, element_callback_t callback, void *contexte) {
for (size_t i = 0; i < taille; i++) {
callback(tableau[i], i, contexte);
}
}
// Callbacks exemples
void afficher_element(int element, size_t index, void *contexte) {
(void)contexte; // Non utilise
printf("tableau[%zu] = %d\n", index, element);
}
void accumuler(int element, size_t index, void *contexte) {
(void)index;
int *somme = (int*)contexte;
*somme += element;
}
void trouver_max(int element, size_t index, void *contexte) {
(void)index;
int *max = (int*)contexte;
if (element > *max) {
*max = element;
}
}
int main(void) {
int tableau[] = {5, 2, 8, 1, 9, 3, 7};
size_t taille = sizeof(tableau) / sizeof(tableau[0]);
printf("Affichage des elements:\n");
parcourir(tableau, taille, afficher_element, NULL);
int somme = 0;
parcourir(tableau, taille, accumuler, &somme);
printf("\nSomme: %d\n", somme);
int max = tableau[0];
parcourir(tableau, taille, trouver_max, &max);
printf("Maximum: %d\n", max);
return 0;
}
Bonnes Pratiques
1. Toujours Initialiser les Pointeurs
// MAUVAIS: pointeur non initialise (valeur aleatoire)
int *p;
*p = 42; // Comportement indefini !
// BON: initialisation a NULL ou valeur valide
int *p = NULL;
int valeur = 42;
int *q = &valeur;
2. Verifier les Allocations
int *buffer = malloc(1024 * sizeof(int));
if (buffer == NULL) {
fprintf(stderr, "Erreur: allocation impossible\n");
return EXIT_FAILURE;
}
// Utilisation sure de buffer
free(buffer);
3. Liberer la Memoire et Reinitialiser
void cleanup(int **ptr) {
if (ptr != NULL && *ptr != NULL) {
free(*ptr);
*ptr = NULL; // Evite les double free et dangling pointers
}
}
4. Utiliser const Quand Possible
// Pointeur vers donnees constantes (ne peut pas modifier la valeur)
void afficher(const int *tableau, size_t taille) {
for (size_t i = 0; i < taille; i++) {
printf("%d ", tableau[i]);
// tableau[i] = 0; // ERREUR: modification interdite
}
}
// Pointeur constant (ne peut pas changer d'adresse)
int valeur = 42;
int *const ptr = &valeur;
*ptr = 100; // OK
// ptr = &autre; // ERREUR
// Pointeur constant vers donnees constantes
const int *const ptr2 = &valeur;
// Ni *ptr2 ni ptr2 ne peuvent etre modifies
5. Eviter l’Arithmetique Hors Bornes
int tableau[10];
int *ptr = tableau;
// DANGER: acces hors bornes
// ptr + 15; // Comportement indefini
// Toujours verifier les bornes
for (int *p = tableau; p < tableau + 10; p++) {
*p = 0; // Sur dans les bornes
}
6. Documenter la Propriete de la Memoire
// La fonction alloue de la memoire - l'appelant doit la liberer
char *creer_chaine(const char *source) {
char *copie = malloc(strlen(source) + 1);
if (copie != NULL) {
strcpy(copie, source);
}
return copie; // L'appelant est responsable de free()
}
// La fonction ne prend pas possession - ne pas liberer
void traiter_chaine(const char *str) {
printf("Traitement: %s\n", str);
// NE PAS free(str) ici !
}
Pieges Courants
1. Dangling Pointers (Pointeurs Pendants)
Un pointeur qui reference une memoire qui a ete liberee ou qui n’existe plus :
// DANGER: memoire liberee
int *creer_dangling(void) {
int *p = malloc(sizeof(int));
*p = 42;
free(p);
return p; // p pointe vers memoire liberee !
}
// DANGER: variable locale
int *pointeur_local(void) {
int local = 42;
return &local; // local n'existe plus apres le return !
}
// SOLUTION: ne pas retourner d'adresses locales
int *solution_allocation(void) {
int *p = malloc(sizeof(int));
if (p != NULL) {
*p = 42;
}
return p; // L'appelant doit liberer
}
2. Fuites Memoire (Memory Leaks)
// FUITE: memoire allouee mais jamais liberee
void fuite_memoire(void) {
int *p = malloc(100 * sizeof(int));
// ... utilisation
// Oubli de free(p) !
}
// FUITE: ecrasement du pointeur
void fuite_ecrasement(void) {
int *p = malloc(sizeof(int));
p = malloc(sizeof(int)); // Premiere allocation perdue !
free(p); // Seule la deuxieme est liberee
}
// SOLUTION: toujours liberer avant de reaffecter
void pas_de_fuite(void) {
int *p = malloc(sizeof(int));
// ... utilisation
free(p);
p = malloc(sizeof(int)); // Nouvelle allocation
// ... utilisation
free(p);
}
3. Double Free
// DANGER: liberer deux fois
int *p = malloc(sizeof(int));
free(p);
free(p); // Comportement indefini !
// SOLUTION: mettre a NULL apres free
int *q = malloc(sizeof(int));
free(q);
q = NULL;
free(q); // free(NULL) est sur (ne fait rien)
4. Buffer Overflow
// DANGER: ecriture hors limites
char buffer[10];
strcpy(buffer, "Cette chaine est trop longue!"); // Debordement !
// SOLUTION: utiliser des fonctions securisees
char buffer2[10];
strncpy(buffer2, "Trop long", sizeof(buffer2) - 1);
buffer2[sizeof(buffer2) - 1] = '\0'; // Garantir terminaison
// Ou mieux: snprintf
char buffer3[10];
snprintf(buffer3, sizeof(buffer3), "%s", "Trop long");
5. Confusion entre Pointeur et Tableau
void fonction(int arr[]) {
// arr est un POINTEUR, pas un tableau !
printf("sizeof(arr) = %zu\n", sizeof(arr)); // Taille d'un pointeur !
}
int main(void) {
int tableau[100];
printf("sizeof(tableau) = %zu\n", sizeof(tableau)); // 400 (100 * 4)
fonction(tableau); // sizeof(arr) = 8 (taille d'un pointeur)
return 0;
}
Conclusion
Les pointeurs sont un outil fondamental en C qui offre un controle direct sur la memoire. Leur maitrise est essentielle pour :
- Ecrire du code performant
- Creer des structures de donnees complexes
- Interfacer avec le systeme et le materiel
- Comprendre le fonctionnement bas niveau des programmes
Tableau Recapitulatif
| Concept | Syntaxe | Description |
|---|---|---|
| Declaration | int *ptr; | Declare un pointeur vers int |
| Adresse de | &variable | Obtient l’adresse d’une variable |
| Dereferencement | *ptr | Accede a la valeur pointee |
| NULL | ptr = NULL; | Pointeur ne pointant vers rien |
| Arithmetique | ptr + n | Avance de n elements |
| Double pointeur | int **pptr; | Pointeur vers un pointeur |
| void* | void *generic; | Pointeur generique |
| Pointeur de fonction | int (*func)(int); | Pointeur vers une fonction |
| const correctness | const int *ptr; | Donnees non modifiables |
| Allocation | malloc(size) | Allocation dynamique |
| Liberation | free(ptr) | Libere la memoire |
Points Cles a Retenir
- Toujours initialiser les pointeurs (a NULL ou une adresse valide)
- Toujours verifier le retour de malloc/calloc/realloc
- Toujours liberer la memoire allouee quand elle n’est plus necessaire
- Eviter les dangling pointers en mettant a NULL apres free
- Utiliser const pour proteger les donnees quand possible
- Documenter la propriete de la memoire dans les interfaces de fonctions
La pratique reguliere et l’utilisation d’outils comme Valgrind pour detecter les fuites memoire vous aideront a maitriser pleinement les pointeurs en C.
References
In-Article Ad
Dev Mode
Mahmoud DEVO
Senior Full-Stack Developer
I'm a passionate full-stack developer with 10+ years of experience building scalable web applications. I write about Vue.js, Node.js, PostgreSQL, and modern DevOps practices.
Enjoyed this article?
Subscribe to get more tech content delivered to your inbox.
Related Articles
Tableaux 2D et Matrices en C : Allocation, Manipulation et Bonnes Pratiques
Maitrisez les tableaux bidimensionnels en C : allocation statique et dynamique, passage aux fonctions, row-major order et optimisation memoire.
Bit-fields et tableaux en C : guide pratique pour optimiser la memoire
Maitrisez les bit-fields et tableaux en C. Apprenez a creer des structures compactes, acceder aux elements et iterer efficacement sur vos donnees.
C : Gestion de mémoire dynamique avec malloc, calloc et free
Maîtrisez la gestion de mémoire dynamique en C avec malloc(), calloc(), realloc(), aligned_alloc() et free(). Guide complet avec exemples, patterns d'allocation et bonnes pratiques.