← Retour au blogArchitecture de processeur

Endianness : big-endian et little-endian (avec schémas de bus)

Le big-endian place l'octet de poids fort à l'adresse la plus basse ; le little-endian inverse l'ordre. Schémas de bus, représentations de 0x12345678 et raisons de la différence entre x86 et TCP/IP.

DennyDenny
11 min de lecture
Chalkboard feature illustration for “Endianness : big-endian et little-endian (avec schémas de bus)”

En bref : l’endianness détermine l’ordre en mémoire des octets d’une valeur multioctet. Le big-endian place l’octet de poids fort à l’adresse la plus basse, comme de nombreux champs de protocoles réseau et de nombreux systèmes PowerPC historiques. Le little-endian y place l’octet de poids faible, comme x86 et les profils RISC-V standard. Dès que des octets franchissent une interface, il faut vérifier séparément l’architecture, le mode de fonctionnement et le protocole.

Un entier de 32 bits occupe 4 octets. La mémoire est adressée un octet à la fois. Lorsque vous écrivez une valeur de 32 bits en mémoire, quel octet va à l’adresse la plus basse ? Ce choix est l’endianness. C’est l’une des rares décisions d’architecture où deux réponses raisonnables persistent côte à côte et où une erreur produit silencieusement de mauvais nombres.

Que signifie « endianness » ?

Prenons la valeur de 32 bits 0x123456780\text{x}12345678. Elle comporte quatre octets :

  • 0x120\text{x}12 — octet de poids fort (MSB)
  • 0x340\text{x}34
  • 0x560\text{x}56
  • 0x780\text{x}78 — octet de poids faible (LSB)

Pour la stocker en mémoire à l’adresse AA, vous devez placer quatre octets aux adresses AA, A+1A+1, A+2A+2, A+3A+3. Les deux principales options sont :

Big-endian. Stocker d’abord le MSB.

AdresseAAA+1A+1A+2A+2A+3A+3
Octet0x120\text{x}120x340\text{x}340x560\text{x}560x780\text{x}78

Little-endian. Stocker d’abord le LSB.

AdresseAAA+1A+1A+2A+2A+3A+3
Octet0x780\text{x}780x560\text{x}560x340\text{x}340x120\text{x}12

La même valeur de 32 bits, les mêmes quatre octets, mais des dispositions en mémoire complètement différentes. Un programme qui écrit 0x123456780\text{x}12345678 sur une machine big-endian et un programme qui lit, octet par octet, quatre octets aux adresses AA..A+3A+3 sur une machine little-endian reconstruiront 0x785634120\text{x}78563412. Les mêmes octets, un nombre erroné.

Les noms viennent d’un essai de Danny Cohen publié en 1980, qui s’inspirait des Voyages de Gulliver : les Lilliputiens se livraient une guerre pour décider par quelle extrémité casser un œuf à la coque. L’analogie voulait montrer que le choix est arbitraire, tout en étant assez important pour qu’un groupe s’y attache au point de déclencher une querelle.

Schéma de bus d’une lecture

La convention d’endianness est appliquée par l’unité de chargement/stockage du processeur. Lorsque le CPU exécute un chargement de 32 bits depuis l’adresse AA, il place AA sur le bus d’adresses et prend 4 octets sur le bus de données.

   CPU                         Memory
+--------+   address=A     +---------+
|  load  | ───────────────>|  byte 0 | A    : 0x12 (big) or 0x78 (little)
| 32-bit |                 |  byte 1 | A+1  : 0x34 (big) or 0x56 (little)
|        |   data[31:0]    |  byte 2 | A+2  : 0x56 (big) or 0x34 (little)
|        | <───────────────|  byte 3 | A+3  : 0x78 (big) or 0x12 (little)
+--------+                 +---------+

Sur un CPU big-endian, l’octet 0 (à l’adresse la plus basse) est décalé dans le registre de 32 bits pour devenir le champ de poids fort ; l’octet 3 arrive dans le champ de poids faible. Sur un CPU little-endian, l’octet 0 arrive dans le champ de poids faible et l’octet 3 dans celui de poids fort.

Les références des composants REGISTER et RAM décrivent les primitives de stockage qui participent à ce transfert. L’endianness est purement une convention de câblage entre elles : quel octet du registre large est relié à quelle adresse d’octet de la mémoire.

Lors d’un accès mémoire synchrone, les octets du bus sont généralement présentés simultanément sur un chemin de données large ; sur un bus 8 bits plus étroit, les mêmes octets arrivent séquentiellement en quatre cycles. La convention d’endianness s’applique de façon identique dans les deux cas.

Exemple détaillé : 0x12345678 en C

Considérons le pseudocode suivant :

uint32_t x = 0x12345678;
uint8_t  *p = (uint8_t *)&x;
printf("%02x %02x %02x %02x\n", p[0], p[1], p[2], p[3]);

Sur une machine big-endian : 12 34 56 78. Sur une machine little-endian : 78 56 34 12. Le pointeur pp parcourt la mémoire octet par octet, et l’ordre des octets est exactement la disposition en mémoire — qui diffère selon l’endianness.

C’est le test standard de détection de l’endianness. C’est aussi, incidemment, une source courante de bogues : le code qui utilise des conversions de pointeur pour « extraire » des octets individuels d’un entier dépend de l’endianness et ne fonctionne sur un hôte donné que sans permutation explicite des octets.

Quelles architectures choisissent quoi ?

ArchitectureEndianness par défaut
x86, x86-64Little-endian
RISC-VLittle-endian (une variante big-endian existe)
ARMBi-endian, configurable ; little-endian très largement répandu
PowerPCBig-endian historiquement ; les cœurs plus récents sont bi-endian
MIPSBi-endian ; la valeur par défaut varie selon le SoC
SPARCBig-endian (V9 est bi-endian)
Motorola 68kBig-endian
z/Architecture (mainframe IBM)Big-endian
Ordre réseau des octets (en-têtes TCP/IP, IPv4, IPv6)Big-endian

La tendance actuelle est fermement orientée vers le little-endian. x86 et ARM représentent ensemble l’immense majorité des CPU en service. Les protocoles réseau restent big-endian pour des raisons historiques : TCP/IP a été spécifié à la fin des années 1970, à une époque où les PDP big-endian de DEC et les mainframes IBM donnaient encore le ton.

Pourquoi deux camps persistent

Les deux choix présentent de vrais avantages, sans qu’aucun ne soit décisif.

En faveur du big-endian

La lecture de haut en bas correspond à l’écriture humaine des nombres. 0x120\text{x}12 est le premier octet que vous voyez en mémoire et aussi le premier groupe de chiffres que vous écririez sur le papier. Un vidage hexadécimal d’une structure big-endian est directement lisible.

L’ordre réseau des octets. Internet est big-endian par spécification (RFC 791, RFC 1700). Dès qu’un protocole binaire est sérialisé sur un fil, le big-endian est la convention établie.

La comparaison du signe est directe. Le bit de signe d’un entier en complément à deux se trouve dans l’octet de poids fort ; une machine big-endian le voit à l’adresse la plus basse, si bien qu’un chargement avec extension de signe n’a qu’à examiner le premier octet récupéré.

En faveur du little-endian

LSB au niveau du LSB. L’octet à l’adresse AA est l’octet de poids faible de l’entier situé à l’adresse AA. Ainsi, un pointeur 32 bits et un pointeur 8 bits vers la même adresse désignent tous deux l’octet de poids faible. Convertir int* en char* ne modifie pas l’adresse ; lire l’octet bas d’une valeur 32 bits à partir d’un registre 16 bits est donc une opération gratuite.

Ordre des retenues arithmétiques. L’addition multiprécision commence par l’octet de poids faible et propage les retenues vers le haut, c’est-à-dire vers les adresses croissantes. Une machine little-endian lit les opérandes et écrit les résultats dans l’ordre naturel de parcours de la mémoire. C’était un véritable argument de performances sur le 8086 en 1978, lorsque les tampons étaient limités.

Chargements indépendants de la largeur. Une valeur 32 bits à l’adresse AA est la même chose qu’une valeur 16 bits à l’adresse AA suivie d’une autre valeur 16 bits à A+2A+2, et la même chose que quatre valeurs 8 bits à partir de AA. La conversion entre largeurs est invisible au niveau de l’octet bas. C’est en partie pourquoi les logiciels x86 ont historiquement toléré des conversions sauvages.

Aucun camp n’a gagné. ARM est le compromis architectural : bi-endian, avec le noyau qui choisit au démarrage.

Quand l’endianness compte

Dans la programmation quotidienne sur une seule machine, l’endianness est invisible. Le CPU ordonne correctement les octets de ses propres types entiers, le compilateur émet les bons chargements et stockages, et le programme n’a jamais besoin d’y penser.

Elle devient visible dès que vous lisez ou écrivez des octets — et non des entiers — qui doivent avoir le même sens ailleurs.

Formats de fichiers binaires. En-têtes ELF, blocs PNG, boîtes MP4, GIF, BMP, ZIP. Certains sont big-endian (PNG), d’autres little-endian (BMP, ZIP) et quelques-uns mélangent les deux. Lire un en-tête ELF sur un hôte d’endianness différente sans permutation renvoie des offsets absurdes.

Protocoles réseau. TCP, UDP, IP, ICMP, DNS, NTP sont tous big-endian. Les fonctions de la bibliothèque C htonl, htons, ntohl, ntohs (« host to network long/short » et leurs inverses) existent précisément pour convertir l’ordre des octets de l’hôte en ordre réseau et inversement. Sur un hôte big-endian, elles ne font rien ; sur un hôte little-endian, elles permutent les octets.

#include <arpa/inet.h>

uint32_t host_value = 0x12345678;
uint32_t net_value  = htonl(host_value);  // 0x78563412 on x86
                                          // 0x12345678 on a big-endian host

Les octets transmis sont identiques sur les deux hôtes. La représentation en mémoire diffère.

Protocoles de périphériques. Mémoire flash SPI, EEPROM I2C, registres de capteurs : la plupart choisissent un ordre des octets dans leur fiche technique et exigent que l’hôte s’y conforme. Beaucoup sont big-endian ; certains sont mixtes.

Conversions de types. Convertir entre des types entiers de largeurs différentes par pointeur ou union en s’attendant à trouver des octets à des offsets fixes est un schéma qui « fonctionne tout seul » sur un hôte et échoue silencieusement sur l’autre.

Registres mappés en mémoire dans les systèmes à endianness mixte. Un CPU big-endian qui commande un périphérique little-endian (ou l’inverse) a besoin d’instructions explicites de permutation des octets à chaque accès au registre.

La règle générale est la suivante : dès que les octets sortent de la garde exclusive du CPU, l’endianness devient un contrat qu’il faut faire respecter. À l’intérieur d’un seul processus sur un seul hôte, vous pouvez l’ignorer. Entre des machines, des fichiers, des protocoles ou des périphériques, vous ne le pouvez pas.

Endianness mixte et middle-endian

Une note de bas de page de l’histoire de l’ordre des octets : quelques systèmes historiques utilisaient un ordre middle-endian, où les valeurs 32 bits étaient stockées comme deux moitiés de 16 bits dans l’ordre big-endian, mais où chaque moitié était elle-même little-endian. Le PDP-11 utilisait cette disposition pour des valeurs 32 bits dans certains modes de fonctionnement. Ainsi, la valeur 0x123456780\text{x}12345678 stockée sur quatre octets devient 0x34,0x12,0x78,0x560\text{x}34, 0\text{x}12, 0\text{x}78, 0\text{x}56 : ni du big-endian pur ni du little-endian pur. Les systèmes modernes n’utilisent plus le middle-endian, mais il refait parfois surface dans d’anciens formats binaires produits par des outils de l’ère PDP.

Certains systèmes Arm distinguaient également le BE-32 historique du BE-8 invariant par octet. La leçon importante est de ne pas déduire la disposition d’un périphérique ou d’un fichier du nom du CPU : utilisez la documentation de l’architecture, du bus et du périphérique pour connaître l’accès exact.

L’endianness dans le cycle fetch-decode-execute

Lorsqu’un CPU exécute une instruction, il doit récupérer le mot d’instruction depuis la mémoire. Sur un CPU big-endian, le mot d’instruction est disposé octet de poids fort en premier ; sur un CPU little-endian, octet de poids faible en premier. La logique de décodage doit correspondre. Cela reste invisible en fonctionnement normal — le CPU ne décode que son propre flux d’instructions, donc sa propre endianness est cohérente — mais cela compte lorsqu’on lit un binaire sur un autre hôte, par exemple pour qu’un débogueur ou un désassembleur examine un binaire construit pour une autre cible.

Le pipeline complet de récupération d’instruction est présenté dans l’article sur le cycle fetch-decode-execute ; l’endianness est le dernier détail à aligner entre la mémoire et le compteur ordinal.

Pourquoi aucun ordre n’est universellement supérieur matériellement

Regarder un vidage hexadécimal de 0x123456780\text{x}12345678 sur x86 — 78 56 34 12 — semble inversé. L’œil lit de gauche à droite, les chiffres sont par paires renversées et la conversion mentale est peu naturelle.

Le little-endian offre bien une propriété pratique : l’adresse d’un entier de plusieurs octets est aussi l’adresse de son octet de poids faible. Une addition multiprécision octet par octet peut avancer vers les adresses croissantes tout en propageant la retenue. Élargir sur place un entier little-endian conserve également l’adresse de sa partie de poids faible.

Le big-endian offre la propriété complémentaire : les octets apparaissent dans l’ordre de leur importance, si bien qu’un vidage mémoire ou une comparaison lexicographique commence par la partie de poids fort. L’ordre réseau des octets utilise cette convention « octet de poids fort en premier » pour les valeurs à plusieurs octets.

Les processeurs modernes chargent normalement un mot entier et réordonnent les octets dans le chemin de chargement/stockage selon les besoins. L’endianness reste essentielle aux frontières mémoire et aux interfaces, mais elle ne modifie pas le résultat booléen d’une addition à propagation de retenue effectuée dans un registre. L’adoption historique reflète des choix d’architecture et de compatibilité, et non l’existence d’un circuit universellement supérieur.

Détecter l’endianness à l’exécution

Un petit contrôle d’exécution en C peut inspecter la représentation d’un objet au moyen d’un pointeur unsigned char, ce que le langage C autorise pour tout objet :

int is_little_endian(void) {
    unsigned int x = 1;
    return *(const unsigned char *)&x == 1;
}

Si l’octet à l’adresse la plus basse vaut 1, l’octet de poids faible est placé en premier. Un résultat faux ne prouve pas à lui seul que l’ordre est big-endian sur une implémentation exotique à endianness mixte.

N’attribuez pas <endian.h>, <sys/endian.h> ou des macros de compilateur telles que __BYTE_ORDER__ à C99 : ce sont des fonctionnalités de la plateforme ou du compilateur. C23 a ajouté des identifiants d’endianness native via <stdbit.h>, mais la prise en charge par les compilateurs déployés et les modes de langage des projets varie. Pour une sérialisation portable, la conception la plus robuste consiste à définir explicitement les octets du protocole et à assembler ou désassembler les entiers avec des décalages, des masques ou une routine de bibliothèque documentée telle que htonl/ntohl.

Pour simuler cela dans DigiSim, construisez une petite boucle lecture-modification-écriture qui charge un octet à une adresse basse, le place dans l’octet de poids fort d’un registre et vérifie le résultat par rapport à un motif attendu — une manière concrète de ressentir l’effet de la différence de câblage sur la valeur. L’article sur le voyage de 3 500 ans du binaire replace cela dans le contexte plus large de l’interaction entre l’ordre des chiffres lisible par l’humain et les commodités de la machine.

Et ensuite ?

L’endianness est l’une des conventions au niveau des octets nécessaires à l’échange de données binaires. Les autres comprennent le codage entier lui-même (arithmétique signée en complément à deux) et le format à virgule flottante (IEEE 754). L’analyse approfondie du cycle fetch-decode-execute relie ces représentations au traitement des instructions.

Ouvrez le modèle de système mémoire RAM de base pour voir une mémoire adressée par octet en action, puis écrivez une valeur 32 bits sur quatre adresses et relisez-la avec un ordre des octets différent afin de ressentir directement la différence de câblage.

Références principales

Transformez l’idée en un circuit fonctionnel.