Comment est-ce possible ? Introduction au paradoxe sizeof
\n\nImaginez : vous créez une structure simple en C++ avec deux champs — char (1 octet) et int (4 octets). Logiquement, on suppose que sa taille sera de 5 octets. Vous écrivez le code, appelez sizeof et... vous obtenez 8 octets. « N'importe quoi ! » — pensez-vous. « Le compilateur est devenu fou ? »
Cette question est l'une des plus populaires sur Stack Overflow (plus de 200 000 vues). Et elle ne concerne pas une erreur du compilateur. Elle concerne la façon dont le processeur et la mémoire vive fonctionnent réellement à bas niveau. Dans cet article, nous allons analyser l'alignement des structures en C++, le concept de padding (remplissage), et apprendre à prédire la taille réelle des structures. Et surtout, nous apprendrons à le contrôler quand c'est vraiment nécessaire.
\n\nPourquoi 1 + 4 ≠ 5 ? La physique du processus
\n\nLes processeurs modernes ne lisent pas la mémoire octet par octet. Ils travaillent par « mots » — des blocs de 4, 8 ou 16 octets (selon l'architecture). Si un entier de 4 octets (int) est situé à une adresse qui n'est pas un multiple de 4, le processeur devra effectuer deux accès mémoire au lieu d'un pour lire ce nombre, puis le « reconstituer » à partir de deux morceaux. C'est catastrophiquement lent.
Pour éviter cela, le compilateur insère des octets « vides » (padding) entre les champs de la structure. Il aligne chaque champ sur une adresse multiple de sa taille. Cela s'appelle l'alignement naturel (natural alignment).
\n\n// Exemple simple qui surprend tout le monde\n#include <iostream>\n\nstruct Example {\n char c; // 1 octet\n int i; // 4 octets\n};\n\nint main() {\n std::cout << "Taille de char : " << sizeof(char) << std::endl; // 1\n std::cout << "Taille de int : " << sizeof(int) << std::endl; // 4\n std::cout << "Taille de struct : " << sizeof(Example) << std::endl; // 8 (!)\n return 0;\n}\n\nÀ quoi cela ressemble-t-il en mémoire ? c occupe l'octet 0. Les octets 1, 2, 3 sont du padding. Et i commence à l'octet 4. Total : 1 (c) + 3 (vide) + 4 (i) = 8 octets.
Les règles d'alignement : comment le compilateur prend sa décision
\n\nLe compilateur suit des règles simples mais strictes. En les connaissant, vous pourrez prédire la taille d'une structure sans exécuter le programme.
\n\n- \n
- Règle 1 : L'ordre des champs est conservé. Le compilateur ne réorganise pas les champs en mémoire (bien que certains compilateurs puissent le faire lors de l'optimisation, la norme ne le garantit pas). \n
- Règle 2 : Chaque champ est aligné sur une adresse multiple de sa taille.
char(1 octet) peut être n'importe où.short(2 octets) — sur des adresses paires.int(4 octets) — sur des adresses multiples de 4.double(8 octets) — sur des adresses multiples de 8. \n - Règle 3 : La taille totale de la structure doit être un multiple de l'alignement du plus grand champ. Ceci est nécessaire pour que les tableaux de structures fonctionnent correctement. Si la plus grande variable est un
double(8 octets), alors la taille de la structure sera un multiple de 8. \n
Analysons un exemple plus complexe :
\n\nstruct Complex {\n char a; // 1 octet\n int b; // 4 octets\n short c; // 2 octets\n char d; // 1 octet\n};\n// Quelle sera la valeur de sizeof ? Comptons ensemble.\n\nAnalyse étape par étape :
\n- \n
a(char) — octet 0. \n b(int) — doit être à une adresse multiple de 4. La prochaine adresse de ce type est 4. Donc, padding : octets 1, 2, 3. Total après b : 8 octets (0-7). \n c(short) — doit être à une adresse paire. La position actuelle est 8 (paire). Parfait. Total après c : 10 octets (0-9). \n d(char) — peut être n'importe où. On le place à l'octet 10. Total après d : 11 octets (0-10). \n