Champs, alignement, padding, unions et allocation
Département de Mathématiques et Informatique, Faculté des Sciences et Techniques, Université Cheikh Anta Diop de Dakar
Ce que la séance construit
Les types composés du C vus en mémoire : une struct range des champs de types différents côte à côte, avec du padding pour respecter l’alignement ; une union superpose ses membres aux mêmes adresses ; les structures se composent, se rangent en tableaux, se manipulent par pointeur et s’allouent sur le tas.
typedef), accéder à ses champs par . et par ->.sizeof d’une structure, et réordonner les champs pour la réduire.D’où l’on vient, où l’on va
Amont. Séance 3 : adresses, pointeurs, sizeof, arithmétique des pointeurs, dumps little-endian, pile et tas, malloc et free.
Cette séance. Les types composés : où le compilateur range chaque champ d’une struct ou d’une union, et pourquoi la taille dépasse souvent la somme des champs.
Aval. Séance 5 : allocation dynamique et structures chaînées (tableau dynamique, listes, piles et files sur le tas), erreurs du tas et intérieur de malloc.
Un tableau range des objets de même type ; une structure range des objets de types différents.
Quoi
Un type composé qui regroupe des membres (champs) de types quelconques sous un même nom. Les champs sont rangés dans l’ordre de la déclaration ; chacun a un offset fixe, connu à la compilation, par rapport au premier octet de la structure.
Pourquoi
Manipuler un enregistrement (un point, une station, un relevé) comme un tout : l’affecter, le passer à une fonction, le ranger dans un tableau, l’allouer sur le tas. s.champ et p->champ ne coûtent qu’une addition d’offset.
Quand ça échoue
Entre les champs, le compilateur insère du padding : sizeof dépasse la somme des champs, memcmp compare aussi ces octets indéterminés, et une structure passée par valeur est copiée en entier.
struct Point {
int x;
int y;
};
int main(void)
{
struct Point p1 = {10, 20}; /* positionnelle */
struct Point p2 = {.y = 20, .x = 10}; /* designee */
struct Point p3 = {.x = 5}; /* y vaut 0 */
struct Point p4 = (struct Point){100, 200};
printf("p1 = (%d, %d)\n", p1.x, p1.y);
printf("p2 = (%d, %d)\n", p2.x, p2.y);
printf("p3 = (%d, %d)\n", p3.x, p3.y);
printf("p4 = (%d, %d)\n", p4.x, p4.y);
printf("sizeof p1 = %zu\n", sizeof p1);
return 0;
}Résultat \(\rightarrow\)
p1 = (10, 20)
p2 = (10, 20)
p3 = (5, 0)
p4 = (100, 200)
sizeof p1 = 8
Lecture. La déclaration crée le type struct Point ; les champs non cités dans l’initialisation valent 0 (p3.y). La forme désignée .y = 20 ne dépend pas de l’ordre des champs.
Adresses simplifiées ; sur la machine, p est en 0x7fffffffece0 et a en 0x7fffffffec90.
Sortie \(\rightarrow\)
a = 10, b = 20
p = (15, 25)
Adresses simplifiées ; sous gdb, p est en 0x7fffffffec60 et q en 0x7fffffffec48, dans les deux appels.
Sortie \(\rightarrow\)
p.x = 1
p.x = 99
Lecture. Dans main : p, 8 octets dans le stack frame de main.
Lecture. Par valeur : l’appel copie les 8 octets de p dans le paramètre q, plus bas dans la pile.
Lecture. q.x = 99 modifie la copie ; p n’est pas touché (gcc avertit : q est modifié mais jamais lu).
Lecture. Au retour, le stack frame est rendu : p.x vaut toujours 1.
Lecture. Par adresse : q reçoit l’adresse de p (0x28), au même emplacement de pile.
Lecture. q->x = 99 écrit à l’adresse contenue dans q : c’est p.x qui change.
Lecture. La modification survit au retour. Passer l’adresse évite aussi de copier une grande structure.
typedef et structure auto-référentielletypedef struct {
int x, y;
} Point;
typedef struct Noeud {
int valeur;
struct Noeud *suivant;
} Noeud;
int main(void)
{
Point p = {3, 4};
Noeud n2 = {2, NULL};
Noeud n1 = {1, &n2};
printf("p = (%d, %d)\n", p.x, p.y);
printf("%d\n", n1.suivant->valeur);
printf("%zu %zu\n", sizeof(Point), sizeof(Noeud));
return 0;
}Résultat \(\rightarrow\)
p = (3, 4)
2
8 16
Lecture. Point s’écrit sans struct ; dans struct Noeud, suivant utilise l’étiquette, car le nom Noeud n’existe qu’après le typedef. 16 octets : 4 + 4 de padding + 8.
| Addr | +0 | +1 | +2 | +3 | +4 | +5 | +6 | +7 |
|---|---|---|---|---|---|---|---|---|
0x00 |
0707 |
0000 |
0000 |
0000 |
fdfd |
ffff |
ffff |
ffff |
0x08 |
2c2c |
0101 |
0000 |
0000 |
dddd |
0000 |
0000 |
0000 |
0x10 |
0808 |
0000 |
0000 |
0000 |
0000 |
0000 |
0000 |
0000 |
Machine little-endian, 64 bits.
Énoncé : donner
a.x et a.y ;p et p->x ;(*p).y et &p->y ;a après a = *p;.Correction
0x00 : x à l’offset 0, 07 00 00 00 \(=\) 7 ; y à l’offset 4, 0xfffffffd \(=\) \(-3\) (complément à deux).0x10 : p = 0x08, l’adresse de b. p->x lit 4 octets en 0x08 : 0x12c \(=\) 300.(*p).y \(\equiv\) p->y : 0x0c, 0xdd \(=\) 221. &p->y \(=\) 0x08 \(+\) offsetof(struct Point, y) \(=\) 0x0c.b dans a : 2c 01 00 00 dd 00 00 00 ; p et b ne changent pas.Les structures précédentes ont montré des octets inutilisés entre les champs : ils viennent de l’alignement.
Quoi
Un objet d’alignement \(n\) (_Alignof) se range à une adresse multiple de \(n\) ; sur x86-64, l’alignement d’un type de base est sa taille. Une structure prend l’alignement de son membre le plus exigeant ; le padding précède un champ qui serait mal aligné et complète la fin.
Pourquoi
Le processeur lit la mémoire par mots alignés : un int à cheval sur deux mots coûterait deux accès (certains processeurs le refusent). Le padding final garde chaque élément d’un tableau aligné.
Quand ça échoue
La taille dépend de l’ordre des champs et de la machine : un format de fichier ou de réseau ne se lit pas en recopiant une struct. Le contenu du padding est indéterminé.
Résultat \(\rightarrow\)
char 1 1
short 2 2
int 4 4
long 8 8
float 4 4
double 8 8
void * 8 8
struct S1 12 4
Lecture. Taille et alignement sont égaux pour les types de base ; struct S1 : 12 octets pour 6 de champs.
Adresses simplifiées ; sur la machine, s est en 0x7fffffffecdc, multiple de 4 (l’alignement de struct S1).
Sortie \(\rightarrow\)
sizeof s = 12
0x7fffffffecdc : 41 7f 00 00 44 33 22 11
0x7fffffffece4 : 5a 7f 00 00
Lecture. Ligne 11 : 12 octets réservés : a à l’offset 0, b à l’offset 4 (multiple de 4), c à l’offset 8 ; les 6 octets hachurés sont du padding.
Lecture. s.a = 'A' écrit un seul octet (41).
Lecture. s.b écrit 4 octets en little-endian, à partir de l’offset 4 : les octets 1 à 3 ne sont pas touchés.
Lecture. s.c = 'Z' : un octet (5a) à l’offset 8.
Lecture. sizeof s vaut 12, et non 6 : la taille compte le padding, y compris les 3 octets de fin.
Lecture. Le dump montre aussi le padding : aucune affectation ne l’a écrit, il garde ce que contenait la pile (7f 00 00).
offsetof et sizeofstruct Data {
char tag;
int value;
short flags;
};
int main(void)
{
size_t somme = sizeof(char) + sizeof(int) + sizeof(short);
printf("tag : %zu\n", offsetof(struct Data, tag));
printf("value : %zu\n", offsetof(struct Data, value));
printf("flags : %zu\n", offsetof(struct Data, flags));
printf("somme des champs : %zu\n", somme);
printf("sizeof : %zu\n", sizeof(struct Data));
printf("_Alignof : %zu\n", _Alignof(struct Data));
return 0;
}Résultat \(\rightarrow\)
tag : 0
value : 4
flags : 8
somme des champs : 7
sizeof : 12
_Alignof : 4
Lecture. offsetof(type, champ) (<stddef.h>) donne l’offset d’un champ en octets. Ici value est à 4 (3 octets de padding après tag), flags à 8, et 2 octets de padding final portent la taille à 12 : la somme des champs n’en fait que 7.
Résultat \(\rightarrow\)
Mauvaise : a 0, b 8, c 16, sizeof 24
Bonne : b 0, a 8, c 9, sizeof 16
1000000 elements : 24000000 contre 16000000 octets
Lecture. Dans Mauvaise, chaque char est suivi de 7 octets de padding pour aligner b et la fin. Ranger les champs par alignement décroissant ne laisse que 6 octets de padding final : 16 octets au lieu de 24, soit 8 000 000 octets (7,6 Mio) de moins pour un million d’éléments.
Résultat \(\rightarrow\)
champs egaux : 1
memcmp : 1
0x7fffffffecd0 : 41 00 00 00 07 00 00 00
0x7fffffffecd8 : 5a 00 00 00
0x7fffffffecdc : 41 ff ff ff 07 00 00 00
0x7fffffffece4 : 5a ff ff ff
Lecture. Champs égaux, padding différent (00, ff) : memcmp voit une différence.
x86-64 : l’alignement de chaque type de base est égal à sa taille.
Énoncé
A, octets de padding, sizeof(struct A) ;B ;C ;A et de C pour réduire leur taille.Correction
Chaque champ à la première adresse multiple de son alignement ; la taille, arrondie au multiple de l’alignement le plus grand.
c 0, 7 octets de padding, d 8, s 16, puis 6 octets de padding final : 24 (alignement 8).s 0, c 2, 1 octet de padding, i 4, t 8, 3 octets de padding final : 12 (alignement 4).c1 0, 7 octets, p 8, c2 16, 3 octets, i 20 : 24, sans padding final.A : d, s, c aux offsets 0, 8, 10 : 16. C : p, i, c1, c2 aux offsets 0, 8, 12, 13 : 16.
Adresses simplifiées ; sur la machine, r est en 0x7fffffffecd0.
Sortie \(\rightarrow\)
sizeof r = 20
offsetof bd.y = 12
hg.x = 25
Lecture. Les deux struct Point sont rangées en place, puis coul ; 3 octets de padding : 20 octets.
Lecture. r.bd.y : offset de bd dans Rect (8), plus offset de y dans Point (4) : octet 12.
Lecture. r.bd.x / 4 lit l’int à l’offset 8 et range 25 à l’offset 0.
Lecture. sizeof r : 20 octets, padding compris.
Lecture. offsetof accepte un membre imbriqué : bd.y est à 12 octets du début.
Lecture. Chaque opérateur . ajoute un offset fixé à la compilation : aucun pointeur n’est stocké.
Adresses simplifiées ; sur la machine, t est en 0x7fffffffecd0 ; padding : octets non montrés.
Sortie \(\rightarrow\)
D 31
T 34
K 38
16
Lecture. sizeof(struct Releve) vaut 8 : ville (1 octet), 3 octets de padding, temp à l’offset 4. Les trois éléments se suivent, de 8 en 8.
Lecture. t devient &t[0] : p contient 0x38.
Lecture. p->ville lit l’octet à l’offset 0, p->temp les 4 octets à l’offset 4.
Lecture. p++ avance d’un élément : \(1\times\texttt{sizeof(struct Releve)} = 8\) octets, 0x40.
Lecture. p désigne t[1].
Lecture. p + 1 : encore 8 octets, 0x48 \(=\) &t[2].
Lecture. p désigne t[2].
Lecture. Entre char *, l’écart se compte en octets : \(2\times8=16\). Le padding se répète dans chaque élément.
| Addr | +0 | +1 | +2 | +3 | +4 | +5 | +6 | +7 |
|---|---|---|---|---|---|---|---|---|
0x40 |
4444 |
a5a5 |
0c0c |
0000 |
5e5e |
0101 |
0000 |
0000 |
0x48 |
545454 |
a5a5 |
0d0d |
0000 |
7878 |
0000 |
0000 |
0000 |
0x50 |
5a5a |
a5a5 |
0e0e0e |
000000 |
c8c8c8 |
050505 |
000000 |
000000 |
Énoncé
sizeof, offsets, padding ;m (44 D, 54 T, 5a Z) ;&m[2].pluie ;q->code et (q + 1)->jour.Correction
code 0, 1 octet de padding (a5, gris), jour 2, pluie 4 : 8 octets, un élément par ligne.{'D', 12, 350}, {'T', 13, 120}, {'Z', 14, 1480} (0x15e, 0x78, 0x5c8).c8 05 00 00.q \(=\) 0x48 : q->code \(=\) 'T' ; q + 1 \(=\) 0x50 : jour \(=\) 14.Une structure range ses membres côte à côte ; une union les superpose.
Quoi
Un type composé dont tous les membres commencent à l’offset 0 et partagent les mêmes octets ; sizeof est celle du plus grand membre, arrondie à l’alignement. Écrire un membre modifie les autres.
Pourquoi
Ranger l’une ou l’autre de plusieurs valeurs sans réserver la place de toutes (variantes d’un message), ou relire les octets d’un objet avec un autre type (un float vu comme entier).
Quand ça échoue
Rien n’indique le dernier membre écrit : en lire un autre réinterprète les octets (permis en C, pas en C++), selon l’endianness et le format des types. Une union discriminée garde ce renseignement dans un champ.
Adresses simplifiées ; sur la machine, d est en 0x7fffffffecf4.
Sortie \(\rightarrow\)
DCBA
i = 0x3f800000
f = -4
sizeof d = 4
Lecture. Une union réserve la place de son plus grand membre : 4 octets, partagés par i, f et c.
Lecture. d.i écrit les 4 octets : 44 43 42 41. Lus comme float, ils valent environ 12,14.
Lecture. c[0] et c[1] : 'D' et 'C' ; rien n’est encore affiché (pas de fin de ligne).
Lecture. "DCBA" : l’ordre des octets en mémoire, little-endian.
Lecture. d.f = 1.0f écrit le motif IEEE 754 de 1,0 : 0x3f800000.
Lecture. Relu comme int, le même motif donne 0x3f800000 : aucune conversion, une réinterprétation.
Lecture. Un seul octet change : le poids fort, qui porte le signe et une partie de l’exposant.
Lecture. 0xc0800000 : signe 1, exposant \(129-127=2\), mantisse 1 : \(-1\times2^2=-4\).
Lecture. sizeof d : 4, la taille du plus grand membre.
Résultat \(\rightarrow\)
entier 221
reel 2.5
texte Thies
sizeof v = 16
Lecture. Le champ type (le discriminant) dit quel membre de val est valide ; afficher le teste avant toute lecture. L’union anonyme val n’a pas de nom de type : ses membres s’écrivent v.val.i, v.val.d, v.val.s.
Adresses simplifiées ; sur la machine, v est en 0x7fffffffecd0 ; le littéral est en 0x555555556026.
Sortie \(\rightarrow\)
entier 221
reel 2.5
texte Thies
sizeof v = 16
Lecture. 16 octets : type (4), 4 octets de padding, puis l’union val, alignée sur 8 à cause du double et du pointeur.
Lecture. ENTIER vaut 0 : l’énumération est rangée comme un int.
Lecture. val.i n’écrit que les 4 premiers octets de l’union.
Lecture. afficher lit type, puis le membre qui lui correspond.
Lecture. Le discriminant change d’abord : REEL (1).
Lecture. 2.5 occupe les 8 octets : 0x4004000000000000.
Lecture. type vaut REEL : afficher lit val.d.
Lecture. val.s reçoit l’adresse du littéral "Thies", en .rodata : 8 octets.
Lecture. Le discriminant dit quel membre lire : rien d’autre ne garde la trace du dernier membre écrit.
| Addr | +0 | +1 | +2 | +3 | +4 | +5 | +6 | +7 |
|---|---|---|---|---|---|---|---|---|
0x00 |
0101 |
0000 |
0000 |
0000 |
0000 |
0000 |
0000 |
0000 |
0x08 |
0000 |
0000 |
0000 |
0000 |
0000 |
0000 |
0c0c |
4040 |
0x10 |
0000 |
0000 |
0000 |
0000 |
0000 |
0000 |
0000 |
0000 |
0x18 |
dddd |
0000 |
0000 |
0000 |
0000 |
0000 |
0c0c |
4040 |
Énoncé
sizeof, offset de val, padding ;v[0].type et la valeur rangée ;v[1].type et la valeur rangée ;v[1].val.d.Correction
val (un double) est alignée sur 8 : offset 8, après 4 octets de padding ; 16 octets.val.d \(=\) 0x400c000000000000 \(=\) \(1{,}75\times2=3{,}5\).val.i \(=\) 0xdd \(=\) 221 (en 0x18).0x400c0000000000dd : un peu plus de 3,5 ; 0x1c à 0x1f restent d’un double antérieur.
Adresses simplifiées ; sur la machine, f est en 0x7fffffffecf4.
Sortie \(\rightarrow\)
sizeof f = 4
0x7fffffffecf4 : cb 00 00 00
mode = 1
0x7fffffffecf4 : c3 00 00 00
Lecture. Les trois champs tiennent dans une unité de 4 octets (unsigned int) ; {0} met les champs à zéro.
Lecture. L’ordre des bits dépend de l’implémentation (C17 6.7.2.1 §11) ; ici gcc, ABI System V x86-64 : le premier champ occupe les bits de poids faible.
Lecture. mode occupe les bits 1 à 3 : \(5 = 101_2\), d’où 0000 1011 \(=\) 0x0b.
Lecture. priorite occupe les bits 4 à 7 : \(12 = 1100_2\), d’où 0xcb.
Lecture. 8 bits utiles, 4 octets : la taille est celle de l’unité de stockage.
Lecture. Le dump confirme cb 00 00 00.
Lecture. 9 ne tient pas sur 3 bits : seuls les 3 bits de poids faible sont gardés, 1. gcc l’avertit (-Woverflow).
Lecture. mode vaut 1, pas 9.
Lecture. Seul le champ mode (bits 1 à 3) est réécrit ; de 101 à 001, seul le bit 3 change : cb devient c3.
int main(void)
{
unsigned char o = 0;
o |= 1u; /* actif : bit 0 */
o |= 5u << 1; /* mode : bits 1 a 3 */
o |= 12u << 4; /* priorite : bits 4 a 7 */
printf("masques : 0x%02x\n", o);
printf("mode = %u\n", (o >> 1) & 0x7u);
printf("Flags1 : %zu\n", sizeof(struct Flags1));
printf("Flags2 : %zu\n", sizeof(struct Flags2));
return 0;
}Résultat \(\rightarrow\)
masques : 0xcb
mode = 5
Flags1 : 4
Flags2 : 8
Lecture. Masques et décalages donnent 0xcb, avec une disposition fixée par le programme. Celle des champs de bits dépend de l’implémentation ; : 0 (ligne 10) ouvre une nouvelle unité : 8 octets.
gcc, x86-64 : le premier champ occupe les bits de poids faible de l’unité.
Énoncé
sizeof e ;e, en binaire puis en hexadécimal ;e.vitesse = 10; : la valeur de e.vitesse et l’octet 0.Correction
unsigned int : 4 octets.niveau \(9=1001_2\) (bits 7 à 4), vitesse \(6=110_2\) (bits 3 à 1), marche 1 (bit 0) : 1001 110 1 \(=\) 0x9d.1001 010 1 \(=\) 0x95.
Adresses simplifiées ; sous gdb, st est en 0x7fffffffec68 (pile), le bloc en 0x5555555592a0 (tas).
Sortie \(\rightarrow\)
Kedougou
Lecture. 16 octets sur le tas : nom (8), releves (4), 4 de padding ; st, sur la pile, en garde l’adresse.
Lecture. st->releves : offset 8 dans le bloc.
Lecture. nom reçoit l’adresse d’un second bloc de 9 octets : la structure ne garde que le pointeur.
Lecture. strcpy écrit les 8 lettres et le terminateur dans le second bloc.
Lecture. st->nom suit deux adresses : st, puis nom.
Lecture. Les membres alloués se libèrent d’abord : après free(st), st->nom ne serait plus lisible.
Lecture. Puis la structure. st contient toujours 0x40 : une adresse qui ne doit plus servir.
Lecture. Trois blocs par conteneur : la structure, le nom, les données. detruire libère les membres, puis la structure (free(NULL) est sans effet). Avec creer("Matam", 4) puis detruire, valgrind affiche « All heap blocks were freed ».
Adresses simplifiées ; sous gdb, les nœuds sont en 0x5555555592a0 et 0x5555555592c0 (tas) ; a et b, sur la pile, contiennent ces adresses.
Sortie \(\rightarrow\)
10
20
Lecture. Un struct Noeud contient un pointeur vers un objet de son propre type : 16 octets, dont 4 de padding avant suivant.
Lecture. Second bloc, 32 octets plus loin : la glibc place 16 octets d’en-tête et d’arrondi entre les deux (non montrés).
Lecture. a->suivant reçoit l’adresse du second nœud : le chaînage n’est qu’une adresse rangée dans un champ.
Lecture. NULL (8 octets à zéro) marque la fin de la liste.
Lecture. p = a : premier tour, p->valeur lit 10.
Lecture. p = p->suivant : p prend 0x60 ; second tour, 20.
Lecture. p prend NULL : la condition est fausse, la boucle s’arrête.
struct Tableau {
size_t longueur;
int donnees[]; /* en dernier */
};
int main(void)
{
size_t n = 4;
struct Tableau *t = malloc(sizeof *t + n * sizeof(int));
t->longueur = n;
for (size_t i = 0; i < n; i++)
t->donnees[i] = 10 * (int)i;
printf("sizeof : %zu\n", sizeof *t);
printf("offsetof : %zu\n", offsetof(struct Tableau, donnees));
printf("donnees[3] = %d\n", t->donnees[3]);
free(t);
return 0;
}Résultat \(\rightarrow\)
sizeof : 8
offsetof : 8
donnees[3] = 30
Lecture. donnees[], dernier champ, n’a pas de taille : sizeof vaut 8. Un seul malloc réserve l’en-tête et les \(n\) éléments (offset 8) ; un seul free rend le tout.
pile.h (vu par le client)
pile.c (seul à connaître les champs)
Compilateur \(\rightarrow\)
s6_opaque.c:7:10: error: storage size of 'q' isn't known
s6_opaque.c:7:10: warning: unused variable 'q' [-Wunused-variable]
Lecture. Le client ne voit qu’une déclaration anticipée : struct Pile est un type incomplet. Un pointeur Pile * se déclare (sa taille est connue), une variable Pile non ; seules les fonctions de pile.c accèdent aux champs, qui peuvent changer sans modifier le client.
| Addr | +0 | +1 | +2 | +3 | +4 | +5 | +6 | +7 |
|---|---|---|---|---|---|---|---|---|
0x100 |
0505 |
0000 |
0000 |
0000 |
0000 |
0000 |
0000 |
0000 |
0x108 |
0000 |
0000 |
0000 |
0000 |
0000 |
0000 |
0000 |
0000 |
0x110 |
0909 |
0000 |
0000 |
0000 |
0000 |
0000 |
0000 |
0000 |
0x118 |
2020 |
0101 |
0000 |
0000 |
0000 |
0000 |
0000 |
0000 |
0x120 |
0202 |
0000 |
0000 |
0000 |
0000 |
0000 |
0000 |
0000 |
0x128 |
0000 |
0101 |
0000 |
0000 |
0000 |
0000 |
0000 |
0000 |
Énoncé
sizeof, offsets, padding ;tete->valeur et tete->suivant ;tete->suivant->valeur et tete->suivant->suivant ;Correction
valeur 0, 4 octets de padding (gris), suivant 8 : 16 octets.0x110 : valeur \(=\) 9, suivant \(=\) 0x120.0x120 : 2 et 0x100.0x100 : 5 et NULL : la liste affiche 9, 2, 5, dans l’ordre des pointeurs, pas des adresses.À retenir
struct range ses champs dans l’ordre de la déclaration, chacun à un offset fixe ; s.c et p->c ((*p).c) ajoutent cet offset à l’adresse de la structure.sizeof \(\geq\) somme des champs, multiple de l’alignement le plus exigeant. offsetof et sizeof le mesurent.memcmp sur des structures.union superpose ses membres à l’offset 0 : sizeof est celle du plus grand ; lire un autre membre réinterprète les octets ; un discriminant dit quel membre est valide.NULL en fin de liste.Séance 5 : allocation dynamique et structures chaînées
Propriété d’un bloc, tableau dynamique et coût amorti, listes chaînées par pointeur de pointeur, piles et files sur le tas, erreurs du tas sous valgrind et AddressSanitizer, et intérieur de malloc (en-têtes, tcache, mini-allocateur first fit).
À préparer
Le TD et le TP de la séance 4 : offsets et padding à calculer, dumps de structures et d’unions à lire, union discriminée et liste chaînée à compléter et à vérifier sous valgrind.
typedef, unions, champs de bits).offsetof).Mesures de la séance : x86-64, Linux 6.18, gcc 13.3.0 (-std=c17 -Wall -Wextra -O0 -g), glibc 2.39, valgrind 3.22.0 ; adresses rendues déterministes par setarch -R, pile de 8192 Kio.
Programmation C avancée — Séance 4 Dr. El Hadji Bassirou TOURÉ • DMI/FST/UCAD
Lecture. Ligne 9 :
poccupe 8 octets :xà l’offset 0,yà l’offset 4, dans l’ordre de la déclaration.Lecture. Ligne 10 :
ptrreçoit l’adresse dep, celle de son premier octet (0x60).Lecture. Ligne 11 :
p.xlit 4 octets à l’offset 0 dep.Lecture. Ligne 12 :
ptr->ylit l’adresse dansptr, ajoute l’offset dey(4) et lit 4 octets en0x64.Lecture. Premier affichage.
Lecture. Ligne 14 : l’écriture passe par l’adresse :
p.xdevient 15 (0f).Lecture. Ligne 15 :
(*ptr).yestptr->y: 20 + 5 = 25 (19). Les parenthèses sont nécessaires :.lie plus fort que*.Lecture.
aetbsont des copies : elles gardent 10 et 20.