TP : Maîtriser la Mémoire et les Structures en C¶
Durée estimée : 3h00
Contexte : Séance non évaluée. L'objectif est de comprendre les mécanismes de la mémoire et des pointeurs étape par étape, sans stress.
Pré-requis : Bases du C (boucles, fonctions, variables).
Partie 1 : L'Allocation Dynamique Simple (malloc)¶
📘 Rappel de cours¶
Jusqu'à présent, vous déclariez des tableaux de taille fixe (ex: int tab[100]). C'est la mémoire statique (allouée sur la pile/stack).
Problème : Si on ne connaît pas la taille à l'avance (elle dépend d'une saisie utilisateur), on est coincé ou on gaspille de la mémoire.
La solution : L'allocation dynamique (dans le tas/heap). On demande manuellement de la mémoire au système.
- Allocation : On utilise
malloc(Memory Allocation).- Syntaxe :
pointeur = (Type*) malloc( nombre_elements * sizeof(Type) );
- Syntaxe :
- Vérification : Toujours vérifier si le pointeur renvoyé n'est pas
NULL(erreur d'allocation, par exemple si la mémoire est pleine). - Libération : Impératif ! Il faut rendre la mémoire avec
free(pointeur)à la fin, c'est à dire lorsqu'on utilise plus la mémoire.
🛠️ Exercice 1 : Mon premier tableau dynamique¶
- Demandez à l'utilisateur de saisir un nombre entier positif
n. - Déclarez un pointeur
int *tab. - Utilisez
mallocpour allouer de la place pournentiers. - Remplissez ce tableau avec les valeurs 1, 2, ..., n.
- Affichez le tableau.
- Libérez la mémoire.
// Exemple de syntaxe pour vous aider :
int *tab = (int*) malloc(n * sizeof(int));
if (tab == NULL) {
exit(1); // Erreur
}
// ... utilisation ...
free(tab);
Partie 2 : Structures - Syntaxe, Copie et Passage par Adresse¶
📘 Rappel de cours¶
1. Définition et typedef¶
Une structure permet de regrouper des variables. En C "pur", on doit répéter le mot struct partout (struct Personne p1;).
Pour simplifier, on utilise typedef souvent combiné à une structure anonyme (sans nom interne) :
typedef struct {
char nom[20];
int age;
int gros_tableau[1000]; // Une structure peut être lourde en octets !
} Personne;
Personne p1; // Plus besoin de "struct", c'est plus propre.
2. Comportement "Primitif" (Copie)¶
C'est un point crucial : Une structure se comporte comme un entier (int).
Si vous faites p2 = p1;, le C effectue une copie intégrale de tous les octets de p1 vers p2. Les deux variables deviennent indépendantes.
3. Passage par Valeur vs Adresse (La Pile vs Le Pointeur)¶
Pourquoi utiliser des pointeurs (Personne *p) ?
- Pour modifier : Si on passe par valeur (copie), la fonction modifie sa copie locale, pas l'original.
- Pour la performance (La Pile/Stack) :
- Par Valeur : La structure entière est copiée sur la "pile" (mémoire temporaire). Si la structure fait 4000 octets, on perd du temps et de la mémoire à copier ces 4000 octets pour rien.
- Par Adresse : On envoie juste l'adresse de la structure (8 octets). C'est instantané, quelle que soit la taille de la structure.
Règle d'or :
- Accès via variable :
p1.age(le point) - Accès via pointeur :
ptr->age(la flèche)
🛠️ Exercice 2 : La "lourdeur" de la copie¶
Copiez et tester ce code. Il contient une structure Joueur volontairement lourde pour illustrer le problème de la copie sur la pile.
#include <stdio.h>
#include <string.h>
// Utilisation de typedef avec une structure anonyme
typedef struct {
char nom[50];
// Un gros tableau pour simuler une structure lourde en mémoire (4Ko)
int donnees_lourdes[1000];
int score;
} Joueur;
// --------------------------------------------------------
// Fonction 1 : Passage par VALEUR (Copie)
// --------------------------------------------------------
// Ici, 'j' est une COPIE locale sur la PILE.
// Toute la structure (4054 octets) a été dupliquée.
void modifierScoreCopie(Joueur j) {
j.score = 100; // On modifie la copie
printf("[Fonction Copie] Score mis a 100.\n");
}
// --------------------------------------------------------
// Fonction 2 : Passage par ADRESSE (Pointeur)
// --------------------------------------------------------
// Ici, on reçoit juste une adresse (8 octets). Pas de duplication.
// On accède à l'original via la flèche '->'
void modifierScorePointeur(Joueur *ptr) {
// TODO : Mettre le score à 500 en utilisant le pointeur et la flèche ->
printf("[Fonction Pointeur] Score mis a 500.\n");
}
int main() {
Joueur j1;
strcpy(j1.nom, "Mario");
j1.score = 0;
printf("Score initial de %s : %d\n", j1.nom, j1.score);
// 1. Essai avec passage par valeur
modifierScoreCopie(j1);
// Rappel : j1 a été copié. L'original n'a pas bougé.
printf("Apres modifierScoreCopie : %d (Devrait etre 0)\n", j1.score);
printf("--------------------------------\n");
// 2. Essai avec passage par adresse
// TODO : Appeler modifierScorePointeur.
// Indice : La fonction attend une adresse, utilisez '&' pour envoyer l'adresse de j1
printf("Apres modifierScorePointeur : %d (Devrait etre 500)\n", j1.score);
return 0;
}
Partie 3 : Tableaux Dynamiques à 2 Dimensions¶
📘 Rappel de cours¶
En C, un tableau 2D dynamique n'est pas un bloc unique. C'est un tableau de pointeurs où chaque pointeur dirige vers un tableau d'entiers (les lignes).
Le type de la variable principale est int** (pointeur de pointeur).
L'allocation se fait en escalier :
- Allouer le tableau vertical de pointeurs (
mallocde taillenblignes * sizeof(int*)). - Boucle
for: Pour chaque case, allouer un tableau horizontal (mallocde taillenbColonnes * sizeof(int)).
🛠️ Exercice 3 : La table de multiplication¶
Écrivez un programme complet qui :
- Demande
nbLignesetnbColonnes. - Alloue la matrice dynamique.
- Remplit la matrice :
matrice[i][j] = (i+1) * (j+1). - Affiche la matrice sous forme de grille alignée.
- Libération (Ordre inverse) : Faites une boucle pour
freechaque ligne d'abord, PUISfreele tableau principal.
Partie 4 : Tableau Dynamique de Structures¶
📘 Rappel de cours¶
On peut combiner malloc et structures. C'est très utilisé pour gérer des bases de données en RAM (inventaires, listes d'étudiants...).
Une fois alloué, cela s'utilise comme un tableau classique.
- Allocation :
Article *stock = (Article*) malloc(n * sizeof(Article)); - Accès :
stock[i].prix(On utilise le point.carstock[i]est la case elle-même, pas un pointeur).
🛠️ Exercice 4 : Gestion de stock¶
Définissez une structure Article contenant : char designation[50] et float prix.
- Demandez le nombre d'articles
n. - Allouez dynamiquement le tableau de structures.
- Saisissez les données (pour simplifier, utilisez
scanf("%s", ...)sans espaces). - Affichez uniquement les articles dont le prix est supérieur à 100€.
- Libérez la mémoire.
Partie 5 : Initiation aux Listes Chaînées¶
📘 Rappel de cours¶
Un tableau oblige à avoir toute la mémoire contiguë (côte à côte). Une liste chaînée est flexible : chaque élément (Maillon) contient une valeur et l'adresse du suivant. Ils sont éparpillés en mémoire et reliés par des fils (pointeurs).
Le dernier élément pointe vers NULL pour dire "C'est la fin".
🛠️ Exercice 5 : Parcours et Nettoyage¶
Le code de gestion de la liste est complexe à écrire au début. Nous vous fournissons le "moteur". Vous devez écrire le parcours.
Copiez-collez ce code et complétez les fonctions vides :
#include <stdio.h>
#include <stdlib.h>
// La structure du Maillon
typedef struct Maillon {
int nombre;
struct Maillon *suivant; // Le lien vers le prochain
} Maillon;
// Fonction utilitaire pour ajouter au début (Fournie)
Maillon* ajouterTete(Maillon* liste, int valeur) {
Maillon* nouvelElement = (Maillon*) malloc(sizeof(Maillon));
nouvelElement->nombre = valeur;
nouvelElement->suivant = liste;
return nouvelElement;
}
// --- A VOUS DE JOUER CI-DESSOUS ---
/**
* Doit parcourir la liste et afficher : 10 -> 20 -> 5 -> NULL
*/
void afficherListe(Maillon* liste) {
Maillon* courant = liste;
// TODO : Écrire la boucle while
// 1. Tant que courant n'est pas NULL
// 2. Afficher courant->nombre
// 3. Avancer : courant prend la valeur de courant->suivant
printf("NULL\n");
}
/**
* Doit libérer toute la mémoire
*/
void toutEffacer(Maillon* liste) {
Maillon* courant = liste;
while (courant != NULL) {
Maillon* aSupprimer = courant;
// TODO : Sauvegarder l'adresse du suivant dans une variable temporaire AVANT de supprimer
// TODO : free(aSupprimer)
// TODO : Passer au suivant (avec la variable temporaire)
}
}
int main() {
Maillon* maListe = NULL; // Liste vide
// Remplissage : 5, puis 8 devant, puis 12 devant.
// La liste sera : 12 -> 8 -> 5 -> NULL
maListe = ajouterTete(maListe, 5);
maListe = ajouterTete(maListe, 8);
maListe = ajouterTete(maListe, 12);
printf("Ma liste chainee :\n");
afficherListe(maListe);
toutEffacer(maListe);
printf("Memoire nettoyee.\n");
return 0;
}
Sujet rédigé à l'aide de Gemini