Exercices Pointeurs en C en PDF (Intermédiaire)
Exercices pointeurs. Un ensemble d'exercices pratiques centrés sur la manipulation des pointeurs en langage C, couvrant déclaration, déréférencement, arithmétique des pointeurs, passage par adresse (ou par pointeur), traitement de chaînes et représentation mémoire des structures. La maîtrise de ces notions est essentielle pour gérer l'accès mémoire, optimiser l'usage des tableaux et implémenter des interfaces bas‑niveau en C; ce guide pratique sert de support d'entraînement et est disponible au format PDF gratuit.
🎯 Ce que vous allez apprendre
- Déclaration et déréférencement des pointeurs — comprendre la syntaxe
char *p, l'utilisation de l'opérateur&pour obtenir une adresse et de*pour accéder à la valeur pointée. Vous saurez déclarer des pointeurs typés et affecter ou lire des données à l'adresse cible sans erreurs de syntaxe ni confusion entre adresse et valeur. - Arithmétique des pointeurs et parcours de tableaux — assimiler comment l'incrémentation d'un pointeur avance de
sizeof(type)octets et comment parcourir un tableau via un pointeur. Vous pourrez écrire des boucles qui itèrent sur des tableaux defloatou decharen utilisant des opérations sur pointeurs plutôt que des indices. - Passage par adresse (ou par pointeur) pour retourner plusieurs résultats — appliquer la convention
void f(..., int *out)pour produire plusieurs valeurs depuis une fonction. Vous pourrez implémenter une fonctioncalculqui remplit des variables via des pointeurs et éviter les pièges liés aux variables locales retournées par adresse. - Traitement de chaînes et manipulation ASCII — parcourir une chaîne jusqu'au caractère terminal
'\0', éliminer des caractères (ex : espaces) et inverser la casse en utilisant la différence ASCII de 32. Vous pourrez réimplémenterstrlenavec un seul pointeur et écrire une fonctionscasequi transforme majuscules/minuscules. - Représentation mémoire des structures et schémas d'adressage — dessiner la carte mémoire d'une structure (par ex. champs
char macdest[6],short int,int) en tenant compte des tailles et de l'alignement implicite. Cet exercice permet d'anticiper l'organisation en octets et d'expliquer le comportement lors d'un accès par pointeurs de types différents. - Algorithme et implémentation conversion binaire→décimal — parcourir une chaîne binaire passée via
argv, calculer le poids fort en fonction de la longueur et accumuler le résultat en décalant le poids. Vous pourrez écrire le programmebtod.cqui vérifieargc, analyse les bits et affiche la valeur décimale. - Allocation statique vs allocation dynamique — introduction à l'allocation dynamique avec
mallocet libération avecfree, vantant la flexibilité par rapport aux tableaux statiques et mettant en garde contre les fuites mémoire.
📑 Sommaire du document
- Exercice 1 : Déclaration et affichage
- Exercice 2 : Arithmétique et flottants
- Exercice 3 : Passage par adresse et retours multiples
- Exercice 4 : Traitement de chaînes et casse ASCII
- Exercice 5 : Représentation mémoire des structures
- Exercice 6 : Conversion binaire → décimal (btod.c)
Les exercices sont progressifs : ils débutent par la déclaration et le déréférencement simples, évoluent vers l'arithmétique de pointeurs et le traitement de chaînes, puis proposent des manipulations plus complexes impliquant des structures en mémoire et des conversions algorithmiques. Cette progression facilite l'apprentissage par étapes et l'application immédiate en TD.
💡 Pourquoi choisir ce cours ?
Ce recueil d'exercices, signé G.VALET et issu du Département IRIS du Lycée polyvalent DIDEROT, associe rappels conceptuels et mises en pratique ciblées. L'approche est résolument orientée sur l'application : travaux sur chaînes, passage par adresse, schémas mémoire et un algorithme guidé pour la conversion binaire. La ressource propose des énoncés exploitables immédiatement en TD, avec des hints algorithmiques plutôt que de simples énoncés abstraits.
Tableaux et pointeurs
L'identificateur d'un tableau en C désigne l'adresse de son premier élément : pour un tableau int t[10], l'expression t est équivalente à &t[0] dans la plupart des contextes. Un pointeur déclaré int *p = t; pointe donc sur le premier élément et l'arithmétique des pointeurs (p + 1) avance de sizeof(int) octets. Cette relation facilite le passage d'un tableau à une fonction qui attend un pointeur et permet d'itérer sans indice en combinant déréférencement et incrémentation. Attention aux exceptions : l'opérateur sizeof appliqué à l'identificateur du tableau renvoie la taille totale du tableau uniquement dans son scope de déclaration, pas lorsqu'il est passé comme paramètre.
Concepts avancés : Allocation et Arithmétique
Les notions d'allocation dynamique et d'arithmétique des pointeurs se combinent dans des usages avancés (réallocations, buffers, structures dynamiques). Comprendre comment déplacer un pointeur en mémoire et comment allouer précisément la quantité nécessaire permet d'écrire des routines performantes et sûres en langage C.
Gestion de l'allocation dynamique
L'utilisation de malloc et free requiert quelques règles claires : inclure la bibliothèque <stdlib.h> pour les prototypes, calculer la taille en octets via sizeof(type) * count, et vérifier systématiquement le retour de malloc avant d'accéder à la mémoire allouée. Exemple minimal de vérification :
int *buf = malloc(n * sizeof(int));
if (buf == NULL) {
perror("malloc");
/* gérer l'erreur : libération d'autres ressources, sortie, etc. */
}
Libérer la mémoire dès qu'elle n'est plus nécessaire avec free et, si approprié, affecter ensuite NULL au pointeur pour éviter les accès après libération. La bibliothèque <stdlib.h> est donc essentielle pour assurer la portabilité et la sécurité des allocations dynamiques.
Pointeurs et tableaux multidimensionnels
Les tableaux 2D se manipulent différemment selon l'allocation : un tableau statique int m[3][4] est contigu en mémoire et peut être parcouru par un pointeur int * en tenant compte des colonnes, tandis qu'un tableau dynamique de pointeurs (int **) peut représenter des lignes séparées. Pour allouer un bloc contigu et accéder par double index, réserver rows * cols * sizeof(int) octets puis utiliser l'arithmétique des pointeurs évite des allocations par ligne et améliore la locality.
Bonnes pratiques de programmation avec les pointeurs
- Initialiser les pointeurs non affectés à
NULLavant utilisation et vérifier leur valeur (if (p == NULL)) avant d'y accéder. - Vérifier le résultat de
mallocpour éviter les accès sur pointeurs invalides; libérer la mémoire avecfreedès que possible et remettre le pointeur àNULLaprès libération. - Documenter les fonctions qui prennent des pointeurs (préconditions : non-NULL, taille minimale) et utiliser des noms de paramètres explicites pour améliorer l'accessibilité du code et la maintenance.
- Éviter de retourner l'adresse d'une variable locale ; préférer le passage par adresse pour que l'appelant fournisse le stockage si nécessaire.
- Utiliser des assertions et des messages d'erreur clairs pour faciliter le débogage des erreurs de déréférencement ou d'accès hors bornes.
Différences entre passage par valeur et par adresse
Le passage par valeur transmet une copie de la donnée à la fonction : les modifications n'affectent pas l'appelant. Pour modifier la variable d'origine, il faut passer son adresse (ou un pointeur vers elle). Le passage par adresse permet donc à la fonction d'écrire directement dans la zone mémoire du paramètre, utile pour retourner plusieurs résultats ou pour modifier des structures complexes. Une variable locale ne doit pas être renvoyée par adresse ; la pratique courante est void f(..., int *out) pour écrire les résultats dans une variable fournie par l'appelant.
❓ Foire Aux Questions (FAQ)
Question — Comment déterminer le poids fort pour la conversion binaire vers décimal ?
Le poids fort correspond à 2^(n-1) où n est la longueur de la chaîne binaire ; on peut le calculer en parcourant la chaîne pour obtenir n puis initialiser une variable poids = 1 << (n-1), ou bien parcourir la chaîne de gauche à droite en multipliant le résultat par 2 et ajoutant le bit courant.
Question — Pourquoi incrémenter un pointeur diffère-t-il d'incrémenter un indice ?
L'incrémentation d'un pointeur avance l'adresse de sizeof(type) octets et reflète le type pointé, tandis qu'un indice manipule une position numérique indépendante du type ; en pratique *(p++) et p[i++] peuvent parcourir une chaîne, mais le pointeur permet des opérations d'arithmétique et d'itération plus directes tout en exigeant vigilance sur la sentinelle '\0' et les accès hors limites.
Question — Comment éviter les pointeurs fous et les segmentation faults ?
Initialiser systématiquement un pointeur non affecté à NULL évite les pointeurs fous. Vérifier if (p == NULL) après un appel à malloc protège contre les déréférencements invalides ; remplacer un pointeur par NULL après free réduit le risque d'accès après libération. Ces précautions réduisent significativement les risques de segmentation fault.
Question — Quelles vérifications faire après un appel à malloc ?
Vérifier que la valeur renvoyée n'est pas NULL, s'assurer que la taille demandée est raisonnable, et prévoir un chemin de gestion d'erreur (libérer d'autres ressources, afficher un message et abandonner proprement) avant de continuer l'exécution. Utiliser des wrappers ou des fonctions utilitaires pour centraliser ces vérifications améliore la robustesse du code.