Ch. 2-3 — Mémoire et pointeurs
La grande rue de la mémoire : adresses, & et sizeof, little-endian — pointeurs et déréférencement, passage par valeur, malloc/free et ses pièges, structures, puis registres du processeur et interruptions.
La grande rue de la mémoire
La mémoire est comme une grande rue : chaque maison — chaque case mémoire — contient 8 bits (un octet) et possède une adresse, généralement notée en hexadécimal. On peut y stocker une variable :
char car = 'B'; /* le code ascii de 'B' est 0x42 */
printf("adresse de car : %p\n", &car); /* ex. 0x7ffc063697b7 */Le contenu de car s'obtient en écrivant car ; son adresse, en écrivant &car.
Si l'on modifie car (car = 'a';), l'adresse ne change pas — il redécore chez lui, mais il
habite toujours à la même adresse.
Une seule maison ne stocke que 8 bits (de 0 à 255) : pour des valeurs plus grandes, il faut
plus de maisons. L'opérateur sizeof donne le nombre d'octets d'un type — il retourne un
long unsigned int, affiché avec %ld :
printf("size of char : %ld octet\n", sizeof(char)); /* 1 */
printf("size of int : %ld octets\n", sizeof(int)); /* 4 */
printf("size of float : %ld octets\n", sizeof(float)); /* 4 */Un int entier = 10; occupe 4 cases contiguës, dont l'adresse d'entier est la première.
L'entier est écrit en little-endian — octets de poids faible en premier (rappel du m99 de
R1.03) : 0x0000000A est rangé 0x0A 0x00 0x00 0x00. Modifier le contenu d'une des cases
intermédiaires modifie donc aussi entier.
Les pointeurs
L'idée : créer une variable qui contient l'adresse d'une autre. Si car habite à
l'adresse 2, un pointeur pt peut contenir 2, et *pt désigne alors le contenu de car — on
parle de déréférencer un pointeur. Un pointeur se déclare selon sa cible : char* pt;,
int* pt;, float* pt; — globalement type* pt; déclare un pointeur vers une case contenant
un objet de ce type.
int entier = 10;
int* pointeur;
pointeur = &entier;
printf("entier vaut : %d\n", entier); /* 10 */
printf("*pointeur vaut : %d\n", *pointeur); /* 10 */
*pointeur = 12;
printf("entier vaut : %d\n", entier); /* 12 ! */Deux étoiles à ne pas confondre
Le * de la déclaration (int* pt;) n'est pas l'opérateur de déréférencement
(*pt = 12;). Les écritures int* pt; et int *pt; sont toutes deux correctes ; ce cours
utilise int* pt;.
Pointeurs et fonctions
Deux règles encadrent les fonctions : les arguments sont passés par valeur — la fonction
travaille sur une copie de la case reçue, à considérer comme une constante — et on ne
retourne qu'une seule valeur. L'astuce du cours : vous ne pouvez pas modifier un argument,
mais vous pouvez toujours modifier la case ciblée par un pointeur. On déclare donc
l'argument comme pointeur et la fonction écrit *pointeur = 4; :
int par_valeur (int a) { a = a + 1; return 0; } /* le main ne voit rien */
int par_pointeur (int * b) { *b = *b + 1; return 0; } /* b du main passe à 1 */Dans le main, après par_valeur(a) puis par_pointeur(pt_b), a vaut toujours 0 et b
vaut 1.
Les tableaux comme pointeurs
Un tableau de taille 4, char tab[4] = {'a','b','c','d'};, place ses cases à la suite
dans la mémoire ; en pratique, tab est simplement un pointeur vers son premier élément.
L'adresse de tab[i] vaut donc tab + i, et son contenu *(tab + i) :
char* pt = &tab[0];
*(pt + 2) = 'e'; /* tab[2] vaut 'e' */
*(tab + 3) = 'f'; /* tab[3] vaut 'f' */Pour passer un tableau en argument, on donne le pointeur vers son début — et comme il
n'existe pas de fonction length en C, on passe souvent la taille en deuxième argument.
Dans la fonction, on s'en sert comme d'un tableau normal ou avec la notation pointeur, en
sachant toujours où est la fin du tableau. Attention : un tableau à deux dimensions (ou
plus) passé en argument perd son aspect bidimensionnel.
malloc, ou comment demander de la mémoire
Retournons la logique : on déclare d'abord le pointeur, puis on lui donne l'adresse d'une case
inutilisée, qui devient la cible de *pt — désormais considérée comme occupée par le
programme. Pour demander de la mémoire : void* malloc(int size);, dans la bibliothèque
stdlib.h. malloc réserve size octets à la suite et retourne un pointeur vers le
premier octet réservé :
#include <stdlib.h>
char* pt;
pt = malloc(1); /* un char fait 1 octet */
*pt = 'B';Un appel malloc(4) réserve 4 octets — de quoi stocker un int ou un tableau de 4 char.
free, ou comment rendre la mémoire
Une case réservée est une case bloquée : si le programme ne l'utilise plus, il faut le
dire au système. free libère l'espace ciblé par un pointeur — et une fois libérées, les
cases peuvent changer de valeurs.
La règle d'or
Toute case mémoire réservée pour le programme doit être libérée par le programme :
pour tout malloc, il doit y avoir un free.
sizeof : les pièges
sizeof est un opérateur (comme +, = ou ==) qui prend exactement un argument — le
nom d'un type (int, char, int*…) — et renvoie le nombre d'octets d'un élément de ce
type. Ne pas l'appliquer à une variable : risque important de mal interpréter le résultat.
printf("size of char* : %ld octets\n", sizeof(char *)); /* 8 octets */
char tab[100];
/* dans main : sizeof(tab) -> 100 octets */
/* dans la fonction sizeof(tableau) -> 8 octets */sizeof(char*) vaut 8 octets (la taille d'un pointeur), et le sizeof d'un tableau reçu en
paramètre donne… la taille du pointeur, pas celle du tableau.
Les structures
Une structure est un regroupement d'autres types. Exemple du cours : un point sur une grille a une position x et une position y — on veut un type qui regroupe les deux :
typedef struct {
int x;
int y;
} point_t;struct définit la structure, typedef annonce qu'on la nomme, point_t est ce nom ; en
mémoire, ses éléments sont stockés à la suite. On l'utilise comme un type normal :
point_t point; puis point.x et point.y — et le plus souvent via des pointeurs :
point_t* pt_point;
pt_point = malloc(sizeof(point_t)); /* l'espace d'un point_t */
pt_point->x = 6; /* équivaut à (*pt_point).x */
pt_point->y = 7;
free(pt_point);pt_point->x désigne la valeur en x de l'élément de type point_t ciblé par pt_point.
Le processeur et les interruptions
Retour sur le m99 de R1.03. Le processeur contient des cases mémoire particulières, les
registres — réservés au processeur, très rapides d'accès (pour m99 : R, A, B, PC,
inst). Son cycle : charger une instruction depuis la mémoire, la décoder,
l'exécuter, passer à la suivante. Son jeu d'instructions — l'ensemble des instructions
exécutables — diffère d'un modèle à l'autre. Les cases mémoire peuvent donc aussi stocker des
instructions en langage machine : la RAM contient le programme compilé, chargé en
mémoire, et le processeur vient l'y lire.
Chaque composant d'entrée/sortie possède son driver. En sortie, le processeur envoie l'information et le périphérique, qui attend, la reçoit puis l'interprète. En entrée (une touche du clavier appuyée), deux mécanismes s'opposent :
- le polling : un bit en mémoire vaut 0 touche relâchée, 1 touche appuyée ; le programme va le lire quand il veut (initiative du programme). Problème : il peut rater un clic s'il ne regarde pas au bon moment ;
- les interruptions : le clavier envoie un message prioritaire au processeur, qui s'arrête pour le lire et le traiter, puis reprend son travail. Avantage : tous les signaux entrants sont captés. Inconvénient : trop de signaux, et la tâche en cours peut ne jamais se réaliser. C'est ce mécanisme qu'utilisent les ordinateurs.
Quand le processeur reçoit une interruption : il sauvegarde son état en mémoire, traite l'interruption — ce qui écrase tout ce qu'il faisait — puis recharge son état depuis les sauvegardes et continue dans le même état qu'avant.
A retenir
- la mémoire est une rue de cases de 1 octet, chacune adressée ;
&vardonne l'adresse,sizeof(type)la taille (char 1, int 4, float 4, pointeur 8) ; - un
inttient dans 4 cases contiguës, écrites en little-endian ; - un pointeur contient une adresse ;
*ptdéréférence — le seul moyen de faire « sortir » une valeur d'une fonction, les arguments étant passés par valeur ; - un tableau est un pointeur vers son premier élément :
tab[i]≡*(tab + i), taille à passer à part ; - tout
mallocappelle sonfree; champs d'une structure :point.xoupt->x; - les entrées se gèrent par interruptions : sauvegarde de l'état, traitement, restauration.
Cours 02.pdf
R2.04 · Cours 2 — Mémoire et pointeurs · 20 pages
Chargement du document…
Cours 03.pdf
R2.04 · Cours 3 — Gestion de la mémoire en C · 23 pages
Chargement du document…
Dernière mise à jour le