TD — Séance 4 — Structures et unions

Offsets, padding, dumps de structures, unions et champs de bits

TD de la séance 4 de Programmation C avancée : champs, alignement, padding, unions et allocation.
Auteur

Dr. El Hadji Bassirou TOURÉ · DMI/FST/UCAD

NoteObjectif

Calculer la disposition d’une structure (offsets, padding, taille), lire le dump d’une structure, d’une structure imbriquée et d’un tableau de structures, réinterpréter des octets par une union, coder des champs de bits, et reconnaître les erreurs de gestion des structures allouées. Sauf mention contraire, la machine est un x86-64 little-endian : char 1 octet, short 2, int et float 4, long, double et pointeurs 8 ; l’alignement de chaque type de base est égal à sa taille. Les adresses sont écrites en hexadécimal.

Exercice 1 : calcul du padding

struct P {
    char a;
    short b;
    char c;
    int d;
};
struct Q {
    int a;
    char b;
    double c;
    char d;
};
struct R {
    char a;
    char b;
    short c;
    char *d;
};
struct T {
    double a;
    char b[5];
    int c;
    short d;
};
  1. Pour chaque structure, compléter le tableau : offset de chaque champ, octets de padding (entre les champs et à la fin), alignement de la structure et sizeof.

    Structure a b c d padding alignement sizeof
    P
    Q
    R
    T
  2. Dessiner la disposition de struct T par lignes de 8 octets, en marquant le padding.

  3. Réordonner les champs de struct Q pour réduire sa taille. Donner les nouveaux offsets, la nouvelle taille, et l’économie en octets et en pourcentage.

  4. Combien d’octets occupe un tableau de 1000 struct Q, avant et après la réorganisation ?

Exercice 2 : lire le dump d’une structure

Une variable e de type struct Etudiant est rangée en 0x80 ; on relève le dump de 0x80 à 0x9f.

Addr +0 +1 +2 +3 +4 +5 +6 +7
0x80 46 61 74 6f 75 00 15 00
0x88 10 00 00 00 cc cc cc cc
0x90 98 00 00 00 00 00 00 00
0x98 46 61 74 69 63 6b 00 cc
struct Etudiant {
    char nom[6];
    short age;
    int note;
    char *ville;
};
struct Etudiant *p = &e;    /* 0x80 */

Codes ASCII utiles : 46 F, 61 a, 63 c, 69 i, 6b k, 6f o, 74 t, 75 u.

  1. Donner l’offset de chaque champ, les octets de padding et sizeof(struct Etudiant). Identifier les octets de padding dans le dump.
  2. Donner la chaîne e.nom. Que lirait printf("%s", e.nom) si nom contenait 6 lettres sans terminateur ?
  3. Donner e.age et e.note, en hexadécimal et en décimal.
  4. Donner p->ville, la chaîne qu’il désigne et sa longueur.
  5. Donner p->ville[3], &p->note et (char *)&p->ville - (char *)p.

Exercice 3 : structures imbriquées et tableau de structures

Addr +0 +1 +2 +3 +4 +5 +6 +7
0x200 65 00 00 00 0f 04 ea 07
0x208 66 00 00 00 03 07 e9 07
0x210 fa 00 00 00 1c 02 e8 07
struct Date {
    char jour;
    char mois;
    short annee;
};
struct Evt {
    int code;
    struct Date d;
};
struct Evt e[3];            /* en 0x200 */
struct Evt *p = e + 1;
  1. Donner sizeof(struct Date) et sizeof(struct Evt), puis l’offset de d et celui de d.annee dans struct Evt, tels que les donne offsetof.
  2. Reconstruire les trois éléments de e (code, puis la date jour/mois/annee).
  3. Donner p, p->d.annee et (p + 1)->code.
  4. Donner l’adresse &e[2].d.mois et l’expression qui l’obtient à partir de p sans crochets.
  5. Donner (char *)&e[2] - (char *)p et &e[2] - p. Pourquoi diffèrent-ils ?

Exercice 4 : unions

union U {
    unsigned int u;
    float f;
    unsigned char o[4];
};
union U x;                  /* en 0x60 */
x.f = -2.5f;
  1. Donner sizeof(union U) et l’offset de chacun des trois membres.

  2. Écrire le motif IEEE 754 de \(-2{,}5\) (signe, exposant, mantisse), puis x.u en hexadécimal.

  3. Remplir le dump de x et donner x.o[0] à x.o[3].

    Addr +0 +1 +2 +3
    0x60
  4. Après x.o[3] = 0x40;, donner x.u et x.f. Quel bit a changé ?

  5. On exécute union { unsigned short s; unsigned char c[2]; } t; t.s = 0x0102;. Dessiner les deux octets de t sur une machine little-endian, puis big-endian, et en déduire une fonction int est_little_endian(void).

Exercice 5 : champs de bits

struct Status {
    unsigned int actif : 1;
    unsigned int mode : 2;
    unsigned int prio : 3;
    unsigned int res : 2;
};
struct Status s = {1, 3, 4, 2};
struct Bits2 {
    unsigned int a : 3;
    unsigned int : 0;
    unsigned int b : 5;
    unsigned int c : 6;
};

Avec gcc sur x86-64, le premier champ occupe les bits de poids faible de l’unité.

  1. Donner sizeof s.
  2. Écrire l’octet 0 de s en binaire (bit 7 à gauche), en marquant chaque champ, puis en hexadécimal.
  3. Après s.prio = 11;, donner la valeur de s.prio et le nouvel octet 0.
  4. Donner sizeof(struct Bits2) et expliquer l’effet de unsigned int : 0;. Donner les 8 octets d’une struct Bits2 mise à zéro, puis remplie avec a = 5, b = 17, c = 33.

Exercice 6 : structures et allocation

Pour chaque fragment, dire ce qui est incorrect, ce qui se passe à l’exécution (ce que signalent gcc, la glibc, valgrind ou AddressSanitizer), et proposer une correction.

struct Personne {
    char *nom;
    int age;
};
/* Fragment 1 */
struct Personne *p = malloc(sizeof *p);
p->nom = "Fatou";
p->age = 25;
free(p->nom);
free(p);
/* Fragment 2 */
struct Personne a;
a.nom = malloc(6);
strcpy(a.nom, "Awa");
struct Personne b = a;
free(a.nom);
free(b.nom);
/* Fragment 3 */
struct Personne *q = malloc(sizeof *q);
q->nom = malloc(7);
strcpy(q->nom, "Cheikh");
free(q);
free(q->nom);
/* Fragment 4 */
struct Chaine {
    size_t len;
    char data[];
};
struct Chaine *s =
    malloc(sizeof(struct Chaine) + 8);
s->len = 8;
strcpy(s->data, "Linguere");
  1. Pour le fragment 2, que valent a.nom et b.nom après la copie ? Écrire une fonction struct Personne copier(const struct Personne *src) qui fait une copie indépendante.
  2. Pour le fragment 4, donner sizeof(struct Chaine) et la taille à allouer.

Exercice 7 (approfondissement) : une liste chaînée dans le tas

Un programme insère trois nœuds struct Noeud { int valeur; struct Noeud *suivant; } en tête d’une liste, chacun alloué par malloc(sizeof(struct Noeud)). On relève le tas autour des nœuds ; dans la première colonne, les adresses sont abrégées à leurs trois derniers chiffres (0x2a0 désigne 0x5555555592a0), mais les octets sont recopiés tels quels. La variable tete vaut 0x5555555592e0.

Addr +0 +1 +2 +3 +4 +5 +6 +7
0x298 21 00 00 00 00 00 00 00
0x2a0 07 00 00 00 00 00 00 00
0x2a8 00 00 00 00 00 00 00 00
0x2b0 00 00 00 00 00 00 00 00
0x2b8 21 00 00 00 00 00 00 00
0x2c0 03 00 00 00 00 00 00 00
0x2c8 a0 92 55 55 55 55 00 00
0x2d0 00 00 00 00 00 00 00 00
0x2d8 21 00 00 00 00 00 00 00
0x2e0 09 00 00 00 00 00 00 00
0x2e8 c0 92 55 55 55 55 00 00
0x2f0 00 00 00 00 00 00 00 00
  1. Donner sizeof(struct Noeud) et l’écart entre deux nœuds consécutifs dans le tas. Que représentent les octets 21 00 00 00 00 00 00 00 en 0x298, 0x2b8 et 0x2d8 ?
  2. En partant de tete, donner la valeur et le champ suivant de chaque nœud, jusqu’à NULL.
  3. Donner les valeurs affichées par un parcours de la liste, puis l’ordre dans lequel les nœuds ont été alloués.
  4. Écrire la boucle qui libère toute la liste, et expliquer pourquoi free(p); p = p->suivant; est incorrect.

Pour cette séance

Le cours

Les slides

À lire en ligne

À télécharger

Retour au sommet