Le langage C divise la mémoire en plusieurs régions distinctes, chacune ayant un rôle spécifique.
1. La pile (Stack)
Caractéristiques
- La pile est gérée automatiquement par le compilateur et le système d'exploitation — aucune intervention manuelle n'est requise.
- Les données résidant sur la pile n'existent que pendant l'exécution de la fonction courante ; elles sont détruites dès que la fonction se termine.
- La pile croît dans le sens des adresses décroissantes (du haut vers le bas). Sa taille maximale est fixée à la compilation : très rapide, mais limitée en capacité.
- Elle suit le principe LIFO (dernier entré, premier sorti) : le dernier élément empillé est le premier à être libéré.
Contenu stocké
- Les variables locales temporaires et les variables locales qualifiées
constsont placées sur la pile. - Lors des appels de fonctions, les paramètres et les valeurs de retour y sont également stockés.
2. Le tas (Heap)
Caractéristiques
- Le tas est géré manuellement par le programmeur via des fonctions d'allocation/libération.
- Il croît dans le sens des adresses croissantes (du bas vers le haut). Sa taille est limitée par la mémoire physique ou virtuelle du système : plus lent que la pile, mais offre une grande flexibilité et un espace important.
Fonctions associées
Allocation dynamique avec malloc :
void *malloc(size_t nb_octets);
- Le paramètre
nb_octetsindique la taille à allouer. - La valeur de retour est un pointeur de type
void*pointant vers le début du bloc alloué. Ce pointeur peut être converti vers tout autre type de pointeur.
Libération de la mémoire avec free :
void free(void *pointeur);
- Le paramètre est l'adresse de départ du bloc précédemment alloué. Tout oubli de libération entraîne une fuite de mémoire.
3. Zone globale (statique)
Description générale
- Cette zone regroupe les variables dont la taille est connue à la compilation et dont la visibilité s'étend à toute la durée d'exécution du programme : variables globales et variables statiques.
- Elle se divise en deux segments : .bss et .data, tous deux accessibles en lecture et en écriture.
Segment .bss
- Les variables globales non initialisées et les variables statiques non initialisées y sont stockées.
- Les variables globales/statiques initialisées à zéro s'y trouvent également.
- Le segment .bss n'occupe pas d'espace dans le fichier exécutable ; son contenu est mis à zéro par le système d'exploitation au chargement.
Segment .data
- Les variables globales initialisées (valeur non nulle) sont placées dans .data.
- Les variables statiques initialisées (valeur non nulle) s'y trouvent aussi.
- Contrairement au .bss, ce segment occupe de l'espace dans le fichier exécutable et est initialisé par le programme lui-même.
4. Zone des constantes
- Les chaînes de caractères littérales et les constantes numériques sont rangées dans cette zone.
- Les variables globales qualifiées
consty résident également. - Pendant toute l'exécution, le contenu de cette zone est non modifiable.
5. Zone de code
- Les instructions machine du programme sont stockées ici. Cette zone est en lecture seule : toute tentative d'écriture provoque une erreur.
- Les chaînes littérales et les constantes définies par
#definepeuvent également s'y trouver selon le compilateur.
Répartition mémoire sur microcontrôleur STM32
1. Mémoire vive — RAM
- La RAM est la mémoire interne avec laquelle le CPU échange directement les données ; on l'appelle aussi mémoire principale.
- Elle est accessible en lecture/écriture à tout moment, très rapide, et sert de support temporaire pour les données des programmes en cours d'exécution.
- Les données sont volatiles : à la coupure de l'alimentation, tout le contenu est perdu.
2. Mémoire morte — ROM (Flash)
- La ROM contient des données écrites avant l'intégration dans le système. En fonctionnement, elle ne peut être que lue, contrairement à la RAM qui permet des écritures rapides.
- Les données stockées en ROM sont stables et persitsantes même après coupure d'alimentation.
L'exemple ci-dessous s'appuie sur un chip STM32F103 avec l'environnement Keil V5. La configuration mémoire par défaut est la suivante :
- La zone ROM démarre à l'adresse
0x08000000avec une taille de0x10000octets. C'est une zone en lecture seule qui héberge la zone de code et la zone des constantes. - La zone RAM démarre à l'adresse
0x20000000avec une taille de0x5000octets. C'est une zone en lecture/écriture qui contient la zone globale, le tas et la pile.
Vérification pratique sur STM32
Code de démonstration
#include "main.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "delay.h"
#include "uart3.h"
#include "led.h"
/* --- Zone globale --- */
int global_noninit; /* variable globale non initialisee */
static int static_noninit; /* variable statique non initialisee */
const int cglobal_noninit; /* variable globale const non initialisee */
int global_init = 1; /* variable globale initialisee */
static int static_init = 2; /* variable statique initialisee */
/* --- Zone des constantes --- */
const int cglobal_init = 3; /* variable globale const initialisee */
int main(void)
{
SystemCoreClockUpdate();
LED_GPIO_Config();
Uart3_init();
while (1)
{
/* --- Pile --- */
int stack_var; /* variable locale non initialisee */
int *stack_ptr; /* pointeur local non initialisee */
int stack_val = 3; /* variable locale initialisee */
char stack_buf[10] = "hello"; /* tableau local initialise */
const int stack_cvar; /* variable locale const non initialisee */
const int stack_cval = 3; /* variable locale const initialisee */
/* --- Tas --- */
int *heap_a = (int *)malloc(sizeof(int));
int *heap_b = (int *)malloc(sizeof(int));
/* --- Zone globale (statique locale) --- */
static int s_local_noninit;
static int s_local_init = 2;
/* --- Zone des constantes --- */
char *str_ptr = "I LOVE YOU";
char *str_num = "5201314";
printf("\n[PILE] Adresses des variables locales\n");
printf(" locale non init : 0x%p\r\n", &stack_var);
printf(" pointeur local non init : 0x%p\r\n", &stack_ptr);
printf(" locale init : 0x%p\r\n", &stack_val);
printf(" tableau local init : 0x%p\r\n", stack_buf);
printf(" const locale non init : 0x%p\r\n", &stack_cvar);
printf(" const locale init : 0x%p\r\n", &stack_cval);
printf("\n[TAS] Adresses allouees dynamiquement\n");
printf(" heap_a : 0x%p\r\n", heap_a);
printf(" heap_b : 0x%p\r\n", heap_b);
printf("\n[ZONE GLOBALE] Adresses des variables globales/statiques\n");
printf(" globale non init : 0x%p\r\n", &global_noninit);
printf(" statique non init : 0x%p\r\n", &static_noninit);
printf(" const globale non init : 0x%p\r\n", &cglobal_noninit);
printf(" globale init : 0x%p\r\n", &global_init);
printf(" statique init : 0x%p\r\n", &static_init);
printf(" statique locale non init : 0x%p\r\n", &s_local_noninit);
printf(" statique locale init : 0x%p\r\n", &s_local_init);
printf("\n[ZONE DES CONSTANTES]\n");
printf(" const globale init : 0x%p\r\n", &cglobal_init);
printf(" pointeur str_ptr : 0x%p\r\n", str_ptr);
printf(" pointeur str_num : 0x%p\r\n", str_num);
printf("\n[ZONE DE CODE]\n");
printf(" entree de main : 0x%p\n", main);
led485_flicker();
delay_ms(1000);
free(heap_a);
free(heap_b);
}
}
Résultats d'exécution
[PILE] Adresses des variables locales
locale non init : 0x20000654
pointeur local non init : 0x20000650
locale init : 0x2000064c
tableau local init : 0x20000640
const locale non init : 0x2000063c
const locale init : 0x20000638
[TAS] Adresses allouees dynamiquement
heap_a : 0x20000060
heap_b : 0x20000068
[ZONE GLOBALE] Adresses des variables globales/statiques
globale non init : 0x20000014
statique non init : 0x20000018
const globale non init : 0x2000001c
globale init : 0x20000020
statique init : 0x20000024
statique locale non init : 0x20000028
statique locale init : 0x2000002c
[ZONE DES CONSTANTES]
const globale init : 0x080011a4
pointeur str_ptr : 0x08000e78
pointeur str_num : 0x08000e84
[ZONE DE CODE]
entree de main : 0x08000d6d