Pointeurs en C – Exercices Corrigés avec Cas Avancés
Ce guide propose des exercices corrigés sur les pointeurs en C, allant des bases aux cas avancés. Il vise à renforcer la compréhension de concepts tels que la manipulation de mémoire, les pointeurs de fonctions, les chaînes de caractères dynamiques et les structures.
Exercice 1 : Déréférencement Basique
Énoncé :
Créez un programme qui utilise un pointeur pour afficher et modifier la valeur d’une variable.
Solution :
#include <stdio.h>
int main() {
int a = 10;
int *p = &a;
printf("Valeur initiale de a : %d\n", *p); // Afficher la valeur via le pointeur
*p = 20; // Modifier la valeur via le pointeur
printf("Nouvelle valeur de a : %d\n", a);
return 0;
}
Points Clés :
- Un pointeur permet de lire et modifier directement la valeur de la variable à laquelle il est lié.
Exercice 2 : Échanger Deux Variables
Énoncé :
Écrivez une fonction qui échange les valeurs de deux variables en utilisant des pointeurs.
Solution :
#include <stdio.h>
void swap(int *x, int *y) {
int temp = *x;
*x = *y;
*y = temp;
}
int main() {
int a = 5, b = 10;
printf("Avant échange : a = %d, b = %d\n", a, b);
swap(&a, &b); // Passer les adresses
printf("Après échange : a = %d, b = %d\n", a, b);
return 0;
}
Points Clés :
- La fonction utilise les adresses des variables pour modifier leurs valeurs directement.
Exercice 3 : Tableaux et Pointeurs
Énoncé :
Implémentez un programme qui affiche les éléments d’un tableau en utilisant des pointeurs.
Solution :
#include <stdio.h>
int main() {
int arr[] = {1, 2, 3, 4, 5};
int *p = arr;
for (int i = 0; i < 5; i++) {
printf("Élément %d : %d\n", i, *(p + i)); // Accès via le pointeur
}
return 0;
}
Points Clés :
*(p + i)
est équivalent àarr[i]
.
Exercice 4 : Chaînes de Caractères Dynamiques
Énoncé :
Écrivez un programme qui copie une chaîne de caractères dans une mémoire allouée dynamiquement.
Solution :
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
char source[] = "Pointeurs en C";
char *dest = (char *)malloc((strlen(source) + 1) * sizeof(char));
if (dest == NULL) {
printf("Erreur d'allocation mémoire\n");
return 1;
}
strcpy(dest, source);
printf("Chaîne copiée : %s\n", dest);
free(dest); // Libérer la mémoire
return 0;
}
Points Clés :
malloc
est utilisé pour allouer suffisamment de mémoire.- N’oubliez jamais de libérer la mémoire allouée avec
free
.
Exercice 5 : Tableaux 2D Dynamiques
Énoncé :
Allouez dynamiquement un tableau 2D, assignez des valeurs, puis affichez-les.
Solution :
#include <stdio.h>
#include <stdlib.h>
int main() {
int rows = 3, cols = 4;
int **matrix = (int **)malloc(rows * sizeof(int *));
for (int i = 0; i < rows; i++) {
matrix[i] = (int *)malloc(cols * sizeof(int));
}
// Remplir le tableau
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
matrix[i][j] = i * cols + j;
}
}
// Afficher le tableau
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%d ", matrix[i][j]);
}
printf("\n");
}
// Libérer la mémoire
for (int i = 0; i < rows; i++) {
free(matrix[i]);
}
free(matrix);
return 0;
}
Points Clés :
- Les tableaux 2D nécessitent une double allocation dynamique.
- Chaque ligne doit être libérée individuellement avant de libérer le tableau principal.
Exercice 6 : Pointeurs de Fonction
Énoncé :
Implémentez un programme qui utilise un pointeur de fonction pour appeler dynamiquement des fonctions.
Solution :
#include <stdio.h>
void add(int a, int b) {
printf("Addition : %d\n", a + b);
}
void multiply(int a, int b) {
printf("Multiplication : %d\n", a * b);
}
int main() {
void (*operation)(int, int);
operation = add;
operation(5, 3); // Appeler add via le pointeur
operation = multiply;
operation(5, 3); // Appeler multiply via le pointeur
return 0;
}
Points Clés :
- Un pointeur de fonction peut stocker l’adresse de différentes fonctions ayant la même signature.
Exercice 7 : Pointeurs Dangling
Énoncé :
Montrez comment éviter un pointeur dangling lors de l’utilisation de free
.
Solution :
#include <stdio.h>
#include <stdlib.h>
int main() {
int *p = (int *)malloc(sizeof(int));
*p = 42;
printf("Valeur : %d\n", *p);
free(p);
p = NULL; // Réinitialiser le pointeur pour éviter un pointeur dangling
return 0;
}
Points Clés :
- Après avoir libéré une mémoire, réinitialisez toujours le pointeur à
NULL
.
Exercice 8 : Accès Concurrent avec Mutex
Énoncé :
Protégez un tableau partagé entre plusieurs threads avec un mutex.
Solution :
#include <stdio.h>
#include <pthread.h>
#define SIZE 5
int arr[SIZE] = {0};
pthread_mutex_t lock;
void *increment(void *arg) {
pthread_mutex_lock(&lock);
for (int i = 0; i < SIZE; i++) {
arr[i]++;
}
pthread_mutex_unlock(&lock);
return NULL;
}
int main() {
pthread_t t1, t2;
pthread_mutex_init(&lock, NULL);
pthread_create(&t1, NULL, increment, NULL);
pthread_create(&t2, NULL, increment, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_mutex_destroy(&lock);
for (int i = 0; i < SIZE; i++) {
printf("%d ", arr[i]);
}
return 0;
}
Points Clés :
- Les mutex assurent un accès sécurisé aux ressources partagées entre threads.
Suite : Exercices Avancés et Complexes avec les Pointeurs en C
Cette suite d’exercices couvre des concepts encore plus avancés avec les pointeurs en C. Les exercices impliquent la gestion de structures imbriquées, les pointeurs void, les allocations complexes, et les erreurs intentionnelles pour apprendre à déboguer.
Exercice 9 : Manipuler des Structures avec Pointeurs
Énoncé :
Créez une structure représentant un étudiant (nom, âge, note moyenne) et utilisez un pointeur pour accéder et modifier ses champs.
Solution :
#include <stdio.h>
#include <string.h>
typedef struct {
char name[50];
int age;
float average;
} Student;
int main() {
Student s1;
Student *ptr = &s1;
// Modifier via pointeur
strcpy(ptr->name, "Alice");
ptr->age = 20;
ptr->average = 17.5;
// Afficher les valeurs
printf("Nom : %s\n", ptr->name);
printf("Âge : %d\n", ptr->age);
printf("Moyenne : %.2f\n", ptr->average);
return 0;
}
Points Clés :
- Utilisez l’opérateur
->
pour accéder aux membres d’une structure via un pointeur. - Combinez les structures avec des pointeurs pour gérer des données dynamiques.
Exercice 10 : Allocation Dynamique de Structures
Énoncé :
Allouez dynamiquement un tableau de structures Student
pour gérer plusieurs étudiants.
Solution :
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char name[50];
int age;
float average;
} Student;
int main() {
int n = 3;
Student *students = (Student *)malloc(n * sizeof(Student));
if (students == NULL) {
printf("Erreur d'allocation mémoire\n");
return 1;
}
// Initialiser les données
for (int i = 0; i < n; i++) {
snprintf(students[i].name, 50, "Etudiant %d", i + 1);
students[i].age = 18 + i;
students[i].average = 15.0 + i;
}
// Afficher les données
for (int i = 0; i < n; i++) {
printf("Nom : %s, Âge : %d, Moyenne : %.2f\n",
students[i].name, students[i].age, students[i].average);
}
free(students);
return 0;
}
Points Clés :
- Chaque élément du tableau est une structure.
- Assurez-vous de libérer la mémoire après utilisation.
Exercice 11 : Utilisation des Pointeurs void
Énoncé :
Utilisez un pointeur void
pour créer une fonction générique qui affiche différentes valeurs.
Solution :
#include <stdio.h>
void printValue(void *ptr, char type) {
switch (type) {
case 'i': // Entier
printf("Valeur entière : %d\n", *(int *)ptr);
break;
case 'f': // Float
printf("Valeur flottante : %.2f\n", *(float *)ptr);
break;
case 'c': // Caractère
printf("Caractère : %c\n", *(char *)ptr);
break;
default:
printf("Type non reconnu\n");
}
}
int main() {
int a = 10;
float b = 3.14;
char c = 'A';
printValue(&a, 'i');
printValue(&b, 'f');
printValue(&c, 'c');
return 0;
}
Points Clés :
- Les pointeurs
void
permettent une flexibilité maximale, mais nécessitent un transtypage explicite.
Exercice 12 : Création d’une Liste Chaînée
Énoncé :
Implémentez une liste chaînée pour stocker des entiers.
Solution :
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node *next;
} Node;
// Ajouter un élément en tête
Node *addNode(Node *head, int value) {
Node *newNode = (Node *)malloc(sizeof(Node));
newNode->data = value;
newNode->next = head;
return newNode;
}
// Afficher la liste
void printList(Node *head) {
Node *current = head;
while (current != NULL) {
printf("%d -> ", current->data);
current = current->next;
}
printf("NULL\n");
}
// Libérer la liste
void freeList(Node *head) {
Node *current = head;
while (current != NULL) {
Node *next = current->next;
free(current);
current = next;
}
}
int main() {
Node *head = NULL;
head = addNode(head, 10);
head = addNode(head, 20);
head = addNode(head, 30);
printList(head);
freeList(head);
return 0;
}
Points Clés :
- Utilisez des pointeurs pour relier les nœuds.
- Gérez soigneusement la libération de mémoire pour éviter les fuites.
Exercice 13 : Fonction Générique avec Pointeurs
Énoncé :
Créez une fonction générique pour trier un tableau en fonction de la taille des éléments.
Solution :
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void sort(void *arr, size_t n, size_t size, int (*cmp)(const void *, const void *)) {
char *base = (char *)arr;
for (size_t i = 0; i < n - 1; i++) {
for (size_t j = i + 1; j < n; j++) {
if (cmp(base + i * size, base + j * size) > 0) {
char temp[size];
memcpy(temp, base + i * size, size);
memcpy(base + i * size, base + j * size, size);
memcpy(base + j * size, temp, size);
}
}
}
}
int compareInt(const void *a, const void *b) {
return (*(int *)a - *(int *)b);
}
int main() {
int arr[] = {5, 2, 9, 1, 6};
size_t n = sizeof(arr) / sizeof(arr[0]);
sort(arr, n, sizeof(int), compareInt);
for (size_t i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
return 0;
}
Points Clés :
- Les fonctions génériques rendent le code réutilisable.
- Combinez
void *
avec des fonctions de comparaison pour généraliser les algorithmes.