Six octets de données, douze octets en mémoire
Le langage C range chaque variable dans des cases de mémoire appelées octets, et le programmeur doit savoir combien elle en prend. Connaître la taille d’une variable et son adresse explique des erreurs qui n’affichent aucun message.
Un logiciel de caisse garde un million de lignes de vente. Chaque ligne a un code d’un octet, une quantité de quatre octets et un rayon d’un octet. Six octets, donc six mégaoctets ? Non : douze. Cette leçon explique pourquoi.
À la fin de cette leçon
Remarque
Je peux dire quelle taille a une variable en C et où elle se place en mémoire.
Mots-clés
| Mot | Ce que ça veut dire | Exemple |
|---|---|---|
| Octet | une case de mémoire qui contient un nombre de 0 à 255 | |
| Type | ce qui dit combien d’octets prend une variable | int |
| Adresse | le numéro d’un octet dans la mémoire | |
| Alignement | la règle qui place une variable sur un multiple de sa taille | |
| Remplissage | les octets perdus que le compilateur ajoute pour aligner | |
| sizeof | l’opérateur qui donne la taille en octets | sizeof(int) |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Chaque type a une taille en octets
Un type dit à la fois quelle sorte de valeur une variable contient et combien d’octets elle prend. Le mot sizeof donne cette taille, et le compilateur la calcule avant même que le programme tourne.
Le type char prend un octet, short deux, int quatre et double huit. Le type long dépend du système : huit octets sous Linux, quatre sous d’autres systèmes. Demandez-le toujours à sizeof.
Un programme qui affiche des tailles
#include <stdio.h>
int main(void) {
printf("%zu %zu %zu %zu\n", sizeof(char), sizeof(short),
sizeof(int), sizeof(double));
return 0;
}Créer un dossier de travail
mkdir -p ~/c && cd ~/cAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Écrire le programme dans un fichier
printf '#include <stdio.h>\nint main(void) {\n printf("%%zu %%zu %%zu %%zu\\n", sizeof(char), sizeof(short), sizeof(int), sizeof(double));\n return 0;\n}\n' > tailles.cAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Compiler et lancer le programme
gcc -Wall -o tailles tailles.c && ./taillesAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Exemple travaillé : ce que le programme a affiché
| Type | Taille | Nombre de valeurs possibles |
|---|---|---|
| char | 1 octet | 256 |
| short | 2 octets | 65 536 |
| int | 4 octets | environ 4,3 milliards |
| double | 8 octets | un nombre à virgule, très grand |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Une adresse est le numéro d’un octet
Imaginez la mémoire comme une longue rangée de casiers numérotés. Chaque casier est un octet, et son numéro est son adresse. Un int occupe quatre casiers qui se suivent, et son adresse est celle du premier.
Les adresses s’écrivent en hexadécimal et changent d’un lancement à l’autre. Les leçons ne citent donc jamais une adresse : elles décrivent sa forme.
Exemple travaillé : l’entier 258 dans quatre casiers
| Casier | Contenu en hexadécimal | Contenu en décimal |
|---|---|---|
| début + 0 | 02 | 2 |
| début + 1 | 01 | 1 |
| début + 2 | 00 | 0 |
| début + 3 | 00 | 0 |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
L’octet de poids faible vient en premier
Le nombre 258 vaut 1 × 256 + 2. Les processeurs courants rangent d’abord l’octet qui pèse le moins, ici 2, puis celui qui pèse 256, ici 1. On dit petit-boutiste (little endian en anglais).
Lire ces quatre octets dans l’ordre inverse donnerait un tout autre nombre.
Le compilateur ajoute du remplissage
Un processeur lit vite un int posé sur une adresse multiple de quatre. Le compilateur respecte donc l’alignement : il saute quelques octets pour placer chaque champ d’une structure sur un multiple de sa taille. Ces octets perdus forment le remplissage.
La structure a ci-dessous a un char, un int puis un char. La structure b a les mêmes champs dans un autre ordre.
Deux structures avec les mêmes champs
struct a { char c; int n; char d; };
struct b { int n; char c; char d; };Exemple travaillé : où chaque champ se place
| Structure a | Décalage | Octets |
|---|---|---|
| char c | 0 | 1, puis 3 de remplissage |
| int n | 4 | 4 |
| char d | 8 | 1, puis 3 de remplissage final |
| Total | 12 |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Écrire le programme des structures
printf '#include <stdio.h>\n#include <stddef.h>\nstruct a { char c; int n; char d; };\nstruct b { int n; char c; char d; };\nint main(void) {\n printf("%%zu %%zu\\n", sizeof(struct a), sizeof(struct b));\n printf("%%zu %%zu\\n", offsetof(struct a, n), offsetof(struct a, d));\n return 0;\n}\n' > champs.cAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Afficher les tailles et les décalages
gcc -Wall -o champs champs.c && ./champsAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Erreur fréquente
Attention
Beaucoup de gens pensent que la taille d’une structure est la somme de ses champs. C’est faux dès qu’un champ doit être aligné. Rangez les champs du plus grand au plus petit pour perdre moins d’octets : ici b prend 8 octets, a en prend 12.