Presque chaque instruction exécutée par un CPU lit ou écrit un registre : une petite case de stockage nommée, située dans le processeur lui-même. La mémoire contient des gigaoctets ; les registres, quelques dizaines de valeurs. Mais c’est là que le travail se fait — calculs, comparaisons et adresses passent tous par eux.
Pourquoi les registres existent
Atteindre la mémoire principale est lent. Un registre se lit en moins d’un cycle ; le cache le plus proche demande quelques cycles ; la mémoire principale peut en demander une centaine ou plus. Chaque valeur qu’une instruction va chercher en mémoire doit d’abord transiter par le bus.
La différence se voit dans le simulateur. Ouvrez l’onglet CPU & buses de la démo ci-dessous et observez le compteur bus cycles en avançant :
- mov ecx, 5
- mov eax, ecx ; registre → registre
- mov DWORD PTR [rbp-4], ecx ; registre → mémoire (écriture)
- mov edx, DWORD PTR [rbp-4] ; mémoire → registre (lecture)
mov eax, ecxcoûte 1 lecture : seulement la lecture de l’instruction elle-même. La valeur circule à l’intérieur du CPU.- L’écriture coûte la lecture de l’instruction plus 1 écriture sur le bus de données.
- La lecture coûte la lecture de l’instruction plus 1 lecture.
Voilà pourquoi les compilateurs s’efforcent de garder les variables les plus utilisées dans des registres, et pourquoi le code non optimisé — qui range chaque variable en mémoire — est bien plus lent.
Les registres généraux
Le x86-64 possède 16 registres généraux de 64 bits. Les huit premiers portent des noms hérités du 8086 16 bits, quand chacun avait sa spécialité :
| Registre | Rôle historique | Usage typique aujourd’hui |
|---|---|---|
rax | accumulateur | valeurs de retour, calcul |
rbx | base | usage général (préservé entre appels) |
rcx | compteur | compteurs de boucle, 4e argument |
rdx | données | 3e argument, moitié haute de mul/div |
rsi | index source | 2e argument, source des chaînes |
rdi | index destination | 1er argument, destination des chaînes |
rbp | pointeur de base | pointeur de cadre (code non optimisé) |
rsp | pointeur de pile | toujours le sommet de la pile |
r8–r15 | (ajoutés par AMD64) | 5e/6e arguments, usage général |
Seul rsp est vraiment spécial : il pointe toujours sur le sommet de la pile, et push, pop, call et ret l’utilisent implicitement. Pour le matériel, les autres sont interchangeables — leurs rôles viennent des conventions, que vous verrez dans les conventions d’appel.
En x86 32 bits, seuls les huit premiers existent, sur 32 bits : eax, ebx, ecx, edx, esi, edi, ebp, esp.
Un registre, plusieurs noms
Chaque registre de 64 bits est accessible par morceaux. Pour rax :
| Nom | Bits | Partie de rax |
|---|---|---|
rax | 64 | tout |
eax | 32 | bits 0–31 |
ax | 16 | bits 0–15 |
ah | 8 | bits 8–15 |
al | 8 | bits 0–7 |
Le même schéma vaut pour rbx/ebx/bx/bh/bl, rcx et rdx. rsi, rdi, rbp et rsp ont des formes 8 bits sil, dil, bpl, spl, et les nouveaux registres utilisent des suffixes : r8d (32), r8w (16), r8b (8).
La démo ci-dessous remplit rax avec un motif reconnaissable, puis écrit dans des morceaux de plus en plus petits. Avancez pas à pas en surveillant rax dans le panneau des registres :
- mov rax, 0x1122334455667788
- mov al, 0xff ; seuls les bits 0–7 changent
- mov ah, 0xee ; seuls les bits 8–15 changent
- mov ax, 0x1234 ; seuls les bits 0–15 changent
- mov eax, 0xdeadbeef ; …et les 32 bits hauts sont effacés !
| Après | rax |
|---|---|
mov al, 0xff | 0x11223344556677ff |
mov ah, 0xee | 0x112233445566eeff |
mov ax, 0x1234 | 0x1122334455661234 |
mov eax, 0xdeadbeef | 0x00000000deadbeef |
La règle d’extension par zéro
Cette dernière ligne surprend tout le monde une fois : écrire un registre 32 bits efface les 32 bits hauts du registre 64 bits, alors qu’écrire un registre 8 ou 16 bits laisse le reste intact.
AMD a conçu le x86-64 ainsi délibérément. Si écrire eax devait préserver la moitié haute, le CPU devrait fusionner l’ancienne et la nouvelle valeur — une dépendance supplémentaire qui ralentit tout. Comme la plupart du code manipule des int 32 bits, rendre ces écritures « propres » est un gros gain.
Cela explique aussi deux idiomes omniprésents dans le code compilé :
mov eax, eaxsemble ne rien faire, mais il étend par zéroeaxdansrax— ce qui convertit ununsigned inten valeur 64 bits.xor eax, eaxest la façon la plus courte de mettre toutraxà 0.
Les registres qu’on ne peut pas écrire avec mov
rip, le pointeur d’instruction, contient l’adresse de la prochaine instruction. On ne l’écrit jamais directement :jmp,call,retet les sauts conditionnels le modifient. On peut le lire via l’adressage :lea rax, [rip+message]calcule l’adresse demessagerelativement à l’instruction courante — c’est ainsi que le code indépendant de la position retrouve ses données.rflagscontient les drapeaux d’état écrits par l’arithmétique et lus par les sauts conditionnels — le sujet du chapitre suivant.
Il existe d’autres registres : les registres SIMD xmm0–xmm15 (et leurs formes plus larges ymm/zmm) pour les flottants et le calcul vectoriel, les registres de segment, et les registres de contrôle utilisés par le système d’exploitation. Les seize registres généraux sont ceux que vous lirez dans presque chaque ligne de désassemblage.
À retenir
- Les registres sont le stockage de travail du CPU ; la mémoire, atteinte par le bus, est bien plus lente.
- Le x86-64 a 16 registres généraux de 64 bits ; seul
rspa un rôle imposé par le matériel. rax,eax,ax,ahetalsont des vues des mêmes bits.- Écrire un registre 32 bits met la moitié haute à zéro ; écrire 8 ou 16 bits, non.
ripetrflagssont modifiés par les instructions elles-mêmes, pas parmov.