La manipulation de pointeurs et l'allocation dynamique de mémoire sont des compétences d'élite qui distinguent les développeurs C confirmés. Cette section consolide votre maîtrise à travers trois défis d'ingénierie mémoire : l'inversion de tableau en place par deux pointeurs convergents, la création d'un vecteur dynamique auto-extensible, et l'allocation complète d'une matrice 2D sur le tas.
Problème 1 : Inversion d'un tableau en place par pointeurs mobiles#
Énoncé#
Écrivez une fonction void InverserTableau(int *tab, int taille) qui inverse l'ordre des éléments d'un tableau en place (c'est-à-dire sans allouer de second tableau intermédiaire en mémoire).
Vous utiliserez deux pointeurs :
- Un pointeur gauche pointant initialement sur le premier élément (tab).
- Un pointeur droite pointant initialement sur le dernier élément (tab + taille - 1).
- Les deux pointeurs échangent leurs valeurs et se rapprochent (gauche++, droite--) jusqu'à se croiser.
flowchart LR
subgraph Iteration1 [Étape 1]
P1["*gauche = 10"] <-->|Échange| P5["*droite = 50"]
end
subgraph Iteration2 [Étape 2]
P2["*gauche = 20"] <-->|Échange| P4["*droite = 40"]
end
Iteration1 --> Iteration2
Solution complète en C#
#include <stdio.h>
void InverserTableau(int *tab, int taille)
{
if (tab == NULL || taille <= 1) return;
int *gauche = tab;
int *droite = tab + taille - 1;
while (gauche < droite)
{
// Échange des valeurs déréférencées
int temporaire = *gauche;
*gauche = *droite;
*droite = temporaire;
// Déplacement convergent des pointeurs
gauche++;
droite--;
}
}
void AfficherTableau(const int *tab, int taille)
{
printf("[ ");
for (int i = 0; i < taille; i++)
{
printf("%d ", tab[i]);
}
printf("]\n");
}
int main(void)
{
int suite[] = {10, 20, 30, 40, 50, 60};
int n = sizeof(suite) / sizeof(suite[0]);
printf("Tableau avant inversion : ");
AfficherTableau(suite, n);
InverserTableau(suite, n);
printf("Tableau apres inversion : ");
AfficherTableau(suite, n);
return 0;
}
Problème 2 : Conception d'un Vecteur Dynamique auto-extensible#
Énoncé#
Dans les langages de haut niveau (comme Python avec les list ou C++ avec std::vector), les listes s'agrandissent automatiquement sans que l'utilisateur n'ait à se soucier de leur taille maximale.
Implémentez ce comportement en C à l'aide de realloc() :
1. Le tableau démarre avec une capacité initiale de 2 éléments.
2. Chaque fois qu'un élément est inséré et que le tableau est plein, la capacité est automatiquement doublée.
3. À la fin, la mémoire totale est proprement libérée.
Solution complète en C#
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int capacite = 2; // Capacité de départ
int taille = 0; // Nombre réel d'éléments stockés
int *vecteur = malloc(capacite * sizeof(int));
if (vecteur == NULL)
{
fprintf(stderr, "Erreur d'allocation initiale.\n");
return 1;
}
printf("--- INSERTION DYNAMIQUE D'ELEMENTS ---\n");
// Simulons l'ajout de 6 éléments successifs
for (int valeur = 10; valeur <= 60; valeur += 10)
{
// Si le vecteur est plein, doubler sa capacité
if (taille == capacite)
{
capacite *= 2;
printf(">> Seuil atteint ! Doublement de la capacite a %d cases...\n", capacite);
int *nouveau_bloc = realloc(vecteur, capacite * sizeof(int));
if (nouveau_bloc == NULL)
{
fprintf(stderr, "Erreur critique : Echec de realloc !\n");
free(vecteur);
return 1;
}
vecteur = nouveau_bloc;
}
vecteur[taille] = valeur;
taille++;
printf("Ajout de %d -> Taille actuelle : %d / Capacite : %d\n", valeur, taille, capacite);
}
printf("\nContenu final du vecteur dynamique : [ ");
for (int i = 0; i < taille; i++)
{
printf("%d ", vecteur[i]);
}
printf("]\n");
// Libération finale
free(vecteur);
vecteur = NULL;
return 0;
}
Problème 3 : Allocation dynamique complète d'une Matrice 2D#
Énoncé#
Lorsqu'une matrice a des dimensions \(N \times M\) inconnues à la compilation, nous devons l'allouer sur le tas en deux étapes :
1. Allouer un tableau de \(N\) pointeurs de lignes (int **matrice).
2. Pour chaque ligne, allouer un tableau de \(M\) entiers.
3. Remplir, afficher et libérer méticuleusement chaque ligne individuellement avant de libérer le tableau de pointeurs.
flowchart TD
PointeurPrincipal["int **matrice\nTableau de N pointeurs"]
PointeurPrincipal --> Ligne0["Ligne 0 : int[M]"]
PointeurPrincipal --> Ligne1["Ligne 1 : int[M]"]
PointeurPrincipal --> Ligne2["Ligne 2 : int[M]"]
Solution complète en C#
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int lignes = 3;
int colonnes = 4;
// 1. Allouer le tableau de pointeurs vers chaque ligne (int*)
int **matrice = malloc(lignes * sizeof(int *));
if (matrice == NULL)
{
fprintf(stderr, "Erreur d'allocation des lignes.\n");
return 1;
}
// 2. Allouer chaque ligne individuellement
for (int i = 0; i < lignes; i++)
{
matrice[i] = malloc(colonnes * sizeof(int));
if (matrice[i] == NULL)
{
// En cas d'échec partiel, libérer les lignes déjà allouées !
for (int k = 0; k < i; k++) free(matrice[k]);
free(matrice);
return 1;
}
}
// 3. Remplissage avec des valeurs de test
int compteur = 1;
for (int i = 0; i < lignes; i++)
{
for (int j = 0; j < colonnes; j++)
{
matrice[i][j] = compteur++;
}
}
// 4. Affichage
printf("Matrice dynamique %d x %d allouee sur le Tas :\n", lignes, colonnes);
for (int i = 0; i < lignes; i++)
{
printf("| ");
for (int j = 0; j < colonnes; j++)
{
printf("%3d ", matrice[i][j]);
}
printf("|\n");
}
// 5. LIBÉRATION MÉDICULEUSE : Lignes d'abord, puis pointeur principal !
for (int i = 0; i < lignes; i++)
{
free(matrice[i]);
matrice[i] = NULL;
}
free(matrice);
matrice = NULL;
printf("\nMemoire de la matrice liberee avec succes (0 fuite).\n");
return 0;
}
Vous possédez désormais la maîtrise des pointeurs et du cycle de vie de la mémoire vive. Dans la Partie 5, nous allons combiner différents types de données sous une même entité avec les structures (struct).
Commentaires