TD — Séance 3 — Organisation de la mémoire

Dumps, pointeurs, adresses, chaînes et tas

TD de la séance 3 de Programmation C avancée : adresses, pointeurs, tableaux, pile et tas.
Auteur

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

NoteObjectif

Lire et écrire l’état de la mémoire octet par octet : retrouver des valeurs dans un dump, calculer des adresses à partir de l’arithmétique des pointeurs, suivre des pointeurs et une double indirection, et reconnaître les erreurs de gestion des chaînes et du tas. Sauf mention contraire, la machine est un x86-64 little-endian : char 1 octet, short 2, int 4, double et pointeurs 8. Les adresses sont écrites en hexadécimal.

Exercice 1 : endianness

La valeur 0x0A1B2C3D est rangée sur 4 octets à partir de l’adresse 0x300. À l’adresse 0x400, un dump a été relevé sur une machine little-endian.

Addr +0 +1 +2 +3
0x300 (big)
0x300 (little)
0x400 9c ff ff ff
  1. Remplir la ligne 0x300 en big-endian, puis en little-endian.
  2. Sur la machine little-endian, donner l’octet lu à l’adresse 0x302.
  3. Sur la machine little-endian, donner en hexadécimal le short lu en 0x300, puis celui lu en 0x302.
  4. Donner en hexadécimal et en décimal l’int rangé en 0x400.
  5. Donner en hexadécimal et en décimal le short rangé en 0x400. Expliquer pourquoi il a la même valeur décimale que l’int.
  6. Donner l’int que lirait une machine big-endian à partir des mêmes 4 octets.

Exercice 2 : lire un dump

On relève le dump suivant, de 0x40 à 0x6f :

Addr +0 +1 +2 +3 +4 +5 +6 +7
0x40 05 00 00 00 a0 0f 00 00
0x48 4d 62 6f 75 72 00 7f 33
0x50 00 00 00 00 00 40 8f 40
0x58 48 00 00 00 00 00 00 00
0x60 18 fc 2c 01 53 4e 00 21
0x68 60 00 00 00 00 00 00 00
int    *pi = (int *)0x40;
char   *ch = (char *)0x48;
double *pd = (double *)0x50;
char  **pc = (char **)0x58;
short  *ps = (short *)0x60;
void  **pv = (void **)0x68;

Codes ASCII utiles : 21 !, 4d M, 4e N, 53 S, 62 b, 6f o, 72 r, 75 u.

Pour chaque expression, donner le type C et la valeur (en hexadécimal, puis en décimal pour les nombres) :

  1. pi[0] et pi[1] ;
  2. la chaîne ch, puis strlen(ch) et *(ch + 2) ;
  3. *pd : les 8 octets lus, puis le signe, l’exposant et la mantisse au format IEEE 754, et la valeur décimale ;
  4. *pc, (*pc)[1], et la chaîne désignée par *pc ;
  5. ps[0] et ps[1] ;
  6. *(short *)(ch + 1) ;
  7. *pv, puis *(short *)*pv ;
  8. strlen((char *)0x64).

Exercice 3 : adresses et arithmétique des pointeurs

short  v[8];        /* v en 0x5000 */
short *p = v + 3;
short *q = &v[7];
int    g[4][5];     /* g en 0x6000 */
double h[3][2];     /* h en 0x6100 */
  1. Donner p et q, puis q - p et (char *)q - (char *)p.
  2. Donner p + 2 et p - 3. Que désigne p - 3 ?
  3. Donner (double *)0x5000 + 3, (int *)0x5000 + 5 et (char *)0x5000 + 5.
  4. Donner &g[2][3] et &g[3][0], en expliquant le calcul.
  5. Donner le type et la valeur de g[2], puis &g[3][4] - &g[0][0].
  6. Donner sizeof g et sizeof g[1].
  7. Donner &h[2][1].

Exercice 4 : l’état de la mémoire ligne par ligne

Le tableau t est rangé en 0x80, le pointeur p en 0x90 et le pointeur q en 0x98.

int t[3] = {7, 8, 9};
int *p = t + 1;
int **q = &p;
*p = *p * 10;
(*q)++;
**q += t[0];
p[-2] = -1;
  1. Compléter le tableau : la valeur de chaque objet après l’exécution de chaque ligne (une case vide si l’objet n’existe pas encore).

    Ligne t[0] t[1] t[2] p q *p **q
    1
    2
    3
    4
    5
    6
    7
  2. Remplir le dump de la zone 0x80 à 0x9f après la ligne 7. Que contiennent les octets 0x8c à 0x8f ?

    Addr +0 +1 +2 +3 +4 +5 +6 +7
    0x80
    0x88
    0x90
    0x98
  3. À la ligne 5, (*q)++ modifie-t-il q, p ou t[1] ? Justifier.

  4. Écrire, sans utiliser p, une expression qui désigne t[2] à partir de q.

Exercice 5 : chaînes de caractères

Le tableau ville est rangé en 0x70 ; son contenu initial est inconnu (noté ??).

char ville[12];
strcpy(ville, "Podor");
strcat(ville, "-Matam");

Codes ASCII utiles : 2d -, 4d M, 50 P, 61 a, 64 d, 6d m, 6f o, 72 r, 74 t.

  1. Remplir le dump de ville après la ligne 2, puis après la ligne 3.

    Addr +0 +1 +2 +3 +4 +5 +6 +7
    0x70
    0x78
  2. Donner strlen(ville) et sizeof ville après la ligne 3.

  3. Donner la chaîne ville + 6 et sa longueur.

  4. Combien d’octets libres reste-t-il dans ville après la ligne 3 ? Une ligne 4 exécute strcat(ville, "!") : que se passe-t-il ?

  5. Avec char *nom = "Podor";, donner sizeof nom et dire si nom[0] = 'p'; est correct.

Exercice 6 : erreurs de gestion du tas

Pour chaque fragment, donner le nombre d’octets alloués, l’erreur commise, sa conséquence (ce que signale gcc, AddressSanitizer ou valgrind, ou ce qui arrive à l’exécution), et une correction.

/* Fragment 1 */
int *v = malloc(10 * sizeof(int));
v = realloc(v, 20 * sizeof(int));

/* Fragment 2 */
for (int k = 0; k < 3; k++) {
    char *s = malloc(32);
    snprintf(s, 32, "releve %d", k);
    printf("%s\n", s);
}

/* Fragment 3 */
int loc[4] = {1, 2, 3, 4};
int *p = loc;
free(p);

/* Fragment 4 */
char *c = malloc(6);
strcpy(c, "Kaffrine");

/* Fragment 5 */
int *w = calloc(4, sizeof(int));
free(w);
w[0] = 1;

Exercice 7 (approfondissement) : inverser l’ordre des octets

Les protocoles réseau transmettent les entiers en big-endian. Une machine little-endian qui envoie l’adresse IPv4 192.168.10.1, soit l’entier 0xC0A80A01, doit donc inverser l’ordre de ses octets.

  1. Donner le dump des 4 octets de x = 0xC0A80A01 sur la machine little-endian.
  2. Écrire la fonction uint32_t inverse_octets(uint32_t x) qui rend l’entier aux octets inversés, à l’aide des seuls opérateurs >>, <<, & et |, une instruction par octet.
  3. Donner, pour x = 0xC0A80A01, la valeur de chacun des quatre termes calculés par la fonction, puis le résultat.
  4. Donner le dump du résultat en mémoire. Que vaut inverse_octets(inverse_octets(x)) ?

Pour cette séance

Le cours

Les slides

À lire en ligne

À télécharger

Retour au sommet