Nombre magique (programmation)
En programmation informatique, le terme magic number (en français « nombre magique ») peut désigner :
- une constante numérique ou un ensemble de caractères utilisé pour désigner un format de fichier ou un protocole[1] ;
- une constante numérique non nommée ou mal documentée ;
- un ensemble de valeurs ayant un sens particulier (par exemple, les GUID).
Indicateur de format
modifierOrigine
modifierCe type de magic number est apparu dans les premières versions du code source de la version 7 d'Unix. Bien qu'il ait perdu son sens originel, le terme a subsisté dans le lexique de l'informatique.
Quand Unix fut porté sur le premier DEC PDP-11/20s, celui-ci n'avait pas de mécanisme de protection de la mémoire et utilisait des références mémoires re-allouables (en)[2]. Ainsi, les versions avant la version 6 d'Unix lisent un fichier exécutable dans la mémoire en sautant à l'offset 0. Avec le développement de la pagination, les versions suivantes d'Unix ont vu le développement des headers précisant les composants d'un fichier exécutable. Une instruction de saut placée au début du header a été développée pour permettre d'exécuter le programme directement (sans lire le header) ; ce qui permet de choisir entre exécuter le programme en utilisant l'ancien mode utilisant des références mémoires réallouables (regular mode) ou en passant par la pagination. Avec le développement des formats d'exécutables, de nouvelles constantes de saut ont été ajoutées en incrémentant l'offset[3].
Dans le Lions' Commentary on UNIX 6th Edition, with Source Code (en) de la version 6 d'Unix, la fonction exec()
lit l'image binaire d'un exécutable à partir du système de fichiers. Les huit premiers octets forment le header qui contient la taille du programme (segment text) et les variables initialisées (segment global). Le premier mot de seize bits de ce header est comparé à deux constantes pour déterminer si l'exécutable utilise des références mémoires réallouables, le système de page en lecture seule récemment développé ou des pages séparées pour les instructions et les données[4]. Dans les sixième et septième versions d'Unix, le double rôle de cette constante du début du header n'était pas précisé mais le bit de poids fort de cette constant était l'opcode de l'instruction de saut sur un PDP-11 (octal 000407 ou hex 0107). Si on ajoute sept au compteur de programme d'un programme exécuté, celui-ci va utiliser le service exec()
pour se lancer.
Le service exec()
lit le header du fichier exécutable (méta) depuis un buffer de l'espace noyau mais l'image exécutable est lue dans l'espace utilisateur et donc sans pouvoir utiliser la constante de saut. Les magic number ont alors été implémentés dans l'éditeur de liens et le chargeur d'Unix ; ils ont dû être utilisés par la suite dans les programmes de test livrés avec les versions 6 et 7 d'Unix.
Dans la version 7, la constante n'est pas lue directement ; elle est d'abord assignée à la variable ux_mag
[5] et fut par la suite désignée par l'expression magic number. Sachant qu'il y avait alors dans cet Unix environ 10 000 lignes de code et beaucoup de constantes utilisées, ce nom est plutôt curieux pour une constante, au moins autant que le commentaire[2] laissé dans la partie concernant le changement de contexte de la version 6 du gestionnaire d'applications d'Unix. C'est probablement pour cela que le terme a ensuite désigné le type d'exécutable, puis étendu aux systèmes de fichiers et étendu encore pour désigner un exécutable utilisant un typage fort.
Dans les données
modifierPlusieurs de ces nombres sont issus d'un typage fort des données ou de leur multiplexage. Ils permettent aux programmes traitant l'information d'identifier les données qui suivent et notamment de distinguer le format de données utilisé.
Exemples
modifier- Les fichiers et flux Unicode peuvent commencer par un indicateur d'ordre des octets (BOM) pour indiquer que commence un texte, en précisant le type d'UTF utilisé et l'ordre éventuel des octets.
- Les fichiers binaires .class de Java commencent toujours par
CAFEBABE
. Décompressé avec Pack200 (en), ce code est changé pourCAFED00D
. Note : «Babe» est une expression familière en anglais pour désignée une fille (Bébée) et D00D en Leet speak correspond au mot anglais «Dude» pouvant se traduire par Mec. - Les images gif utilisent le code ASCII
GIF89a
(47 49 46 38 39 61
) ouGIF87a
(47 49 46 38 37 61
). - Les images JPEG commencent par
FF D8
et finissent parFF D9
. Les images JPEG/JFIF contiennent le code ASCII pourJFIF
(4A 46 49 46
) et se terminent par une chaîne de caractères vide. Les images JPEG/Exif contiennent le code ASCII pourExif
(45 78 69 66
) et se terminent aussi par une chaîne nulle suivie d'autres métadonnées. - Les images png commencent par une signature de huit octets :
\211 P N G \r \n \032 \n
(89 50 4E 47 0D 0A 1A 0A
). Cette signature permet la détection de problèmes de transmission : vu qu'elle contient des sauts de ligne (« \n »), cela permet de détecter par exemple les sauts de fin de ligne ajoutés automatiquement lors d'un transfert en mode ASCII par ftp (au lieu d'utiliser le mode binaire). - Les fichiers MIDI standards commencent par
MThd
(4D 54 68 64
) suivi d'autres métadonnées. - Les scripts Unix commencent toujours par un shebang «
#!
» (23 21
) suivi par l'adresse de l'interpréteur de commandes à exécuter. - Les fichiers et le programme PostScript commencent par «
%!
» (25 21
). - Les anciens exécutables .exe de DOS et les nouveaux portable executable de Microsoft Windows commencent par la chaîne
MZ
(4D 5A
) ; ce sont les initiales du concepteur de ces formats, Mark Zbikowski (en). Le codeZM
(5A 4D
) est aussi possible mais il est plus rare. - Dans le système de fichiers UFS, les données des superblocks (en) sont repérées par les codes
19 54 01 19
ou01 19 54
selon la version utilisée. Tous deux correspondent à la date de naissance de leur concepteur, Marshall Kirk McKusick. - Le Master boot record des périphériques amorçables de toutes les machines IA-32 Compatible PC finit par
AA 55
. - Les exécutables pour consoles portables Game Boy et Game Boy Advance commencent par une séquence de 48 et respectivement 156 octets qui correspond à l'encodage bitmap du logo de Nintendo.
- Les archives zip commencent par «
PK
» (50 4B
), les initiales de Phil Katz qui est l'auteur de l'utilitaire de compression DOS PKZIP. - Les exécutables pour Amiga (les Amiga Hunk (en)) exécutables sur Amiga classic 68000 commencent par la chaîne hexadécimale « $000003f3 » surnommée « Magic cookie ».
- Les premières versions de l'écran noir de la mort des Amiga – appelés aussi Guru Meditation qui surviennent lors d'une erreur non identifiable – affichent le code
48454C50
qui correspond en ASCII à «HELP
» (48=H, 45=E, 4C=L, 50=P). - La seule adresse absolue d'un système Amiga est
$0000 0004
(emplacement adresse 4) qui contient le système d'amorçage « Sysbase », un pointeur versexec.library
, le noyau du système. - Les exécutables PowerPC PEF (en) utilisés sur Mac OS et BeOS commencent par la chaîne ASCII de «
Joy!
» :4A 6F 79 21
. - Les images TIFF commencent par
II
ouMM
suivi par «42
» (2A
en hexadécimale) – en référence au livre de Douglas Adams La grande question sur la vie, l'univers et le reste – comme entier encodé sur deux octets en petit ou en gros-boutiste selon le type de processeur. «II
» est utilisé sur Intel (petit-boutiste) ce qui donne le code49 49 2A 00
. «MM
» est utilisé sur Motorola (gros-boutiste) :4D 4D 00 2A
. - Les fichiers textes Unicode encodés en UTF-16 commencent généralement par un indicateur d'ordre des octets pour détecter l'endianness (
FE FF
pour les big-endian etFF FE
pour les little-endian). Les fichiers UTF-8 commencent le plus souvent parEF BB BF
. - Les bytecodes pour LLVM commencent par
BC
(42 43
). - Les fichiers wad, utilisés par les jeux basés sur le moteur id Tech 2, commencent par
WAD2
(Quake et dérivés) ouWAD3
(Quake II et dérivés).
Détection
modifierSous unix, la commande file
permet de repérer le format d'un fichier à partir d'une signature[6]. Il existe plusieurs projets tentant de les énumérer[7],[8].
Dans les protocoles
modifier- Le protocole OSCAR utilisé par AIM/ICQ préfixe les requêtes par
2A
. - Dans le protocole RFB utilisé par VNC, le programme client commence par envoyer
RFB
(52
46
42
) suivi par le numéro de version du protocole utilisé par le client. - Dans le protocole SMB utilisé par Windows, chaque requête et chaque réponse du serveur commencent par
FF
53
4D
42
, c'est-à-dire en convertissant l'hexadécimal en ASCII :\xFFSMB
. - Dans le protocole MSRPC (en) de Windows, chaque requête TCP commence par
05
pour « Microsoft DCE/RPC Version 5 », suivi par00
ou01
pour le numéro de version mineur. Les requêtes UDP commencent toujours par04
. - Les interfaces COM et DCOM sérialisées comme OBJREF (en) commencent toujours par « MEOW » (
4D
45
4F
57
). Les extensions de débogage utilisées par DCOM commencent par « MARB » (4D
41
52
42
). - Une requête non chiffrée d'un tracker BitTorrent commence par
13
(qui représente la longueur de l'entête), suivi par la phrase « BitTorrent protocol ». - Les paquets eDonkey2000 et eMule ne faisant qu'un octet contiennent la version du client utilisée : à l'heure actuelle
E3
représente un client eDonkey,C5
un client eMule etD4
un client eMule utilisant la compression. - Les transactions SSL commencent toujours par le message «
client hello
». Le schéma d'encapsulation des paquets SSL réserve deux ou trois octets pour l'entête. Généralement, un «client hello
» d'un client SSL version 2 commence par80
et la réponse d'un serveur version 3 à un client commence par16
(mais cela peut varier). - Les paquets DHCP utilisent un magic cookie
63
82
53
63
au début de la section option de tous les paquets. Ce nombre magique correspond à la séquence ASCII « ?R5? » (en traitant les valeurs hexadécimales comme des valeurs décimales), est défini dans la révision 5 du protocole BOOTP précurseur du DHCP.
Constantes numériques non nommées
modifierLe terme de magic number peut également correspondre à l'utilisation de constantes numériques non nommées dans le code source d'un programme. L'utilisation de ces constantes viole les anciennes règles de programmation issues de COBOL, de FORTRAN et de PL/I[9], ne rend pas plus clair le choix de cette valeur[10] et provoque généralement des erreurs de programmation. Selon certains, le nommage de toutes les constantes rend le code plus lisible, plus compréhensible et plus facilement maintenable[11].
Le nom des constantes doit avoir un sens selon le contexte ; par exemple, il vaut mieux éviter les codes du genre SEIZE = 16
alors que NOMBRE_DE_BITS
aurait été plus clair.
Les problèmes avec ces magic number ne se limitent pas au constantes numériques ; le terme est également utilisé pour d'autres types de données, la déclaration des constantes étant plus flexible et porteuse de sens[9]. Ainsi, déclarer const string testNomUtilisateur = "Jean"
est meilleur que d'utiliser le mot-clé "Jean
" de manière disséminée dans le programme ; de plus, cela simplifie la phase de tests.
Par exemple, le pseudo-code suivant permet de mélanger aléatoirement les valeurs d'un tableau représentant un jeu de 52 cartes :
for i from 1 to 52 j := i + randomInt(53 - i) - 1 jeu.swapEntries(i, j)
où jeu
est un objet de type tableau, la fonction randomInt(x)
choisit un nombre au hasard entre 1 et x compris et swapEntries(i, j)
échange la position des données placées en i et j dans le tableau. Dans cet exemple, 52
est un magic number. Il est plutôt conseillé d'écrire :
constant int nombreDeCartes := 52 for i from 1 to nombreDeCartes j := i + randomInt(nombreDeCartes + 1 - i) - 1 jeu.swapEntries(i, j)
Et ce pour plusieurs raisons :
- Le second exemple est plus facile à lire et à comprendre. Un développeur lisant pour le premier exemple va se demander « Pourquoi 52 ? » bien qu'il serait certainement capable de comprendre en lisant attentivement l'ensemble du code. Les magic number deviennent déroutants lorsque la même valeur est utilisée pour désigner des choses complètement différentes dans une même partie de code.
- Il est plus simple de modifier la valeur de la constante car celle-ci n'est pas dupliquée. Changer la valeur d'un magic number est source d'erreur car la même valeur est très souvent utilisée à plusieurs endroits dans le programme. Il se peut également que deux variables aux significations différentes aient la même valeur ; cela pose un problème pour les différencier. Pour adapter le premier exemple à un jeu de tarot de 78 cartes, le développeur pourrait naïvement remplacer toutes les occurrences de 52 par 78. Cela pourrait causer deux problèmes : premièrement, la valeur 53 de la seconde ligne de l'exemple ne sera pas modifiée et amènerait le programme à ne traiter qu'une partie du jeu de cartes ; deuxièmement, il se peut qu'il remplace la chaîne "52" dans tout le programme sans prêter attention à ce qu'elle signifie ce qui introduirait très certainement des bugs. Par comparaison, changer la valeur de
nombreDeCartes
dans le second exemple est une opération très simple et ne nécessite la modification que d'une seule ligne. - La déclaration des variables remplaçant les magic number sont placées au début des fonctions ou fichiers pour faciliter leur recherche et leur modification.
- L'introduction de paramètres est plus facile. Par exemple, pour généraliser l'exemple et faire qu'il puisse mélanger n'importe quel tas de cartes, il faudrait passer
nombreDeCartes
en paramètre de la procédure :
function melange(int nombreDeCartes ) for i from 1 to nombreDeCartes j := i + randomInt(nombreDeCartes + 1 - i) - 1 jeu.swapEntries(i, j)
- Il est plus facile de détecter les coquilles car une variable déclarée est vérifiée par le compilateur. Par exemple, saisir "62" au lieu de "52" ne sera pas détecté alors que « nombreDeCates » au lieu de « nombreDeCartes » lèvera un warning (ou une erreur) du compilateur informant que « nombreDeCates » n'est pas défini.
- Cela permet également de minimiser les saisies au clavier dans les IDE permettant le complètement automatique : il suffit de saisir les premières lettres de la variable pour que l'IDE le complète automatiquement.
Il y a tout de même quelques défauts :
- L'exécution de l'expression
nombreDeCartes + 1
prend plus de temps que l'expression53
. Néanmoins, la plupart des compilateurs et des interpréteurs modernes sont capables de comprendre que la variablenombreDeCartes
a été déclarée comme constante et de pré-calculer la valeur 53 dans le code compilé. - L'utilisation de grands noms de variables allonge les lignes de code, obligeant certaines lignes de code à s'étaler sur plusieurs lignes.
- Le débogage peut être plus difficile si le débogueur ne fait pas la correspondance entre les noms de variable et leur valeur.
Usages acceptés comme constantes numériques non nommées
modifierDans certains cas – dépendant des habitudes de codage –, l'utilisation de constantes numériques non nommées est acceptée :
- l'utilisation du 0 ou du 1 comme valeur initiale ou valeur d'incrémentation dans les boucle for :
for (int i=0;i<max;i=i+1)
(en supposant que l'incrémentationi++
n'est pas supportée) ; - l'utilisation de 2 dans les expressions mathématiques :
périmètre = rayon * Math.PI * 2
; - l'utilisation de 2 pour vérifier qu'un nombre est pair ou impair :
bool estPair = (x%2==0)
, où%
est l'opérateur modulo.
Les constantes 1 et 0 sont parfois utilisées pour représenter les valeurs booléennes « Vrai » et « Faux » dans les langages de programmation qui n'ont pas ce type (comme les anciennes versions de C).
En C/C++, 0 est parfois utilisé pour représenter un pointeur ou référence null. Comme pour les valeurs booléens, les bibliothèques de C standards contiennent des macro-définition de NULL
dont l'utilisation est fortement conseillée. D'autres langages proposent des valeurs null
ou nil
spécifiques.
Magics GUIDs
modifierIl est possible de créer ou modifier des GUID pour qu'ils soient facilement mémorisables bien que cela puisse affecter leur efficacité et leur unicité[12]. Les règles pour générer des GUID et des UUID sont complexes mais assurent d'avoir des numéros uniques si elles sont suivies scrupuleusement.
Plusieurs GUID java commencent par « CAFEEFAC
».
Nombre magique de débogage
modifierCertains « nombres magiques » consistent en une valeur particulière qui est inscrite dans la mémoire vive pendant l'allocation de mémoire pour faciliter le débogage en cas de plantage. Pendant le débogage, le contenu de la mémoire est généralement affiché en hexadécimal. Des valeurs judicieusement choisies pour former des séquences répétitives de lettres hexadécimales, ou des mots en Hexspeak, sont plus facilement identifiables par un opérateur humain.
L'utilisation de valeurs impaires est particulièrement utile sur des processeurs incapables d'adresser la mémoire au niveau de l'octet, car ceux-ci planteront s'ils essaient s'en servir comme pointeur. De même, on préférera les valeurs qui ne font pas partie du jeu d'instructions.
Vu qu'il est rare qu'un entier codé sur 32 bit prenne une valeur aussi particulière, l'apparition d'un de ces nombres dans un débogueur ou dans un core dump indique généralement un dépassement de tampon ou une variable non initialisée.
Code | Description |
---|---|
..FACADE |
Utilisé par beaucoup de systèmes d'exploitation temps réel |
8BADF00D |
Utilisé par Apple comme code d'exception dans l'iPhone quand une application a pris trop de temps pour se lancer ou se terminer |
A5A5A5A5 |
Utilisé sur les systèmes embarqués car la séquence binaire correspondante (10100101) est facilement reconnaissable sur un oscilloscope ou un analyseur logique |
ABABABAB |
Utilisé par la fonction HeapAlloc() de Microsoft pour marquer le « no man's land » d'un Guard byte (en) après l'allocation de la mémoire heap
|
ABADBABE |
Utilisé par Apple comme « Boot Zero Block » |
ABADCAFE |
Valeur d'initialisation utilisée pour démasquer les pointeurs brisés |
BAADF00D |
Utilisé par la fonction LocalAlloc(LMEM_FIXED) de Microsoft pour marquer la mémoire heap allouée mais non initialisée
|
BADBADBADBAD |
Utilisé sur les calculateurs de Burroughs Corporation pour repérer la mémoire non initialisée (mot de 48-bit) |
BADC0FFEE0DDF00D |
Utilisé sur le système 64 bits RS/6000 pour indiquer les registres processeurs non initialisés |
BADCAB1E |
Code d'erreur retourné par le débogueur eVC quand la connexion avec le débogueur est coupée |
BADDCAFE |
Sous Solaris, marque la mémoire du noyau qui n'est pas initialisée (KMEM_UNINITIALIZED_PATTERN) |
BEEFCACE |
Utilisé sur le framework .NET comme nombre magique pour les fichiers de ressources. |
C0DEDBAD |
Utilisé pour le débogage des tables MMU. |
CAFEBABE |
Dans les exécutables Mach-O (Fat binary (en) dans les processeurs 68k et les PowerPC) pour identifier les fichiers objet et les .class java |
CAFEFEED |
Sous Solaris, marque la mémoire allouée par la fonction kmemfree() pour le débogage
|
CCCCCCCC |
Utilisé par la libraire de débogage du C++ de Microsoft pour repérer la mémoire stack non initialisée |
CDCDCDCD |
Utilisé par la libraire de débogage du C++ de Microsoft pour repérer la mémoire heap non initialisée |
CEFAEDFE |
Peut-être vu dans les binaires Mach-O de Mac OS X (voir FEEDFACE )
|
DDDDDDDD |
Utilisé par MicroQuill's SmartHeap et le débogueur mémoire C++ de Microsoft pour marquer la mémoire heap libérée |
DEADBABE |
Marque le début des fichiers arena d'IRIX |
DEADBEEF |
Connu pour être utilisé sur les systèmes IBM (notamment sur RS/6000), le premier Mac OS (OPENSTEP) et sur le Commodore Amiga. Sous Solaris, marque la mémoire du noyau libérée (KMEM_FREE_PATTERN) |
DEADDEAD |
Le code d'erreur STOP de Windows utilisé quand l'utilisateur crash volontairement la machine |
DEADF00D |
Marque toutes les nouvelles zones mémoires allouées quand elles n'ont pas été explicitement nettoyées après une corruption |
DEADFA11 |
Utilisé par Apple comme code d'exception dans l'iPhone quand l'utilisateur a forcé une application à s'éteindre |
EBEBEBEB |
Pour MicroQuill's SmartHeap |
FADEDEAD |
Marque la fin des scripts AppleScript |
FDFDFDFD |
Utilisé par le débogueur mémoire C++ de Microsoft pour marquer le « no man's land » d'un Guard byte (en) avant et après la mémoire heap allouée |
FEE1DEAD |
Utilisé par l'appel système reboot() de Linux
|
FEEDFACE |
Peut être vu sur le binaire PowerPC Mach-O de Mac OS X. Sous Solaris, marque la zone rouge (KMEM_REDZONE_PATTERN) |
FEEEFEEE |
Utilisé par la fonction HeapFree() de Microsoft pour marquer la mémoire heap libérée
|
La plupart de ces valeurs ont une taille de 32 bits : la taille d'un mot sur les processeurs 32 bits.
L'usage répandu de telles valeurs dans les technologies de Microsoft n'est pas qu'une simple coïncidence, car ces dernières sont largement commentées dans le livre de Steve Maguire (en) Writing Solid Code (en), chez Microsoft Press (en). Il donne plusieurs critères pour les choisir :
- Elles ne doivent pas être d'usage courant ; « 0 » ne satisfait pas ce critère ;
- Elles doivent être facilement reconnaissables dans un débogueur ;
- Sur les machines sans alignement de données à l'octet, elles doivent être impaires pour que leur déréférencement provoque une exception ;
- Elles doivent provoquer des exceptions ou un point d'arrêt si elles sont exécutées comme une instruction.
Comme elles sont souvent utilisées pour identifier des zones mémoires censées être vides, elles tendent parfois à être utilisées dans le langage courant dans le sens « perdu, abandonné, vidé de la mémoire » : par exemple, « Ton programme est DEADBEEF »[13].
Le langage de programmation ZUG de Pietr Brandehörst initialisait la mémoire avec les valeurs 0000
, DEAD
ou FFFF
en phase de développement, et à 0000
en phase de production, pour que les variables non initialisées restent détectables par les développeurs mais compromettent le moins possible la stabilité du programme en production[réf. nécessaire].
Notes et références
modifier- (en) Cet article est partiellement ou en totalité issu de l’article de Wikipédia en anglais intitulé « Magic number (programming) » (voir la liste des auteurs).
- (en) The Linux Information Project, « Magic Number Definition », (consulté le )
- Odd Comments and Strange Doings in Unix (en) « http://cm.bell-labs.com/cm/cs/who/dmr/odd.html »(Archive.org • Wikiwix • Archive.is • Google • Que faire ?) (consulté le ).
- Personal communication with Dennis M. Ritchie
- Version six system1 source file [1]
- Version seven system1 source file [2]
- (en) Gary C. Kessler, « Linux Magic Numbers » (consulté le )
- (en) Richard Ogley, « File magic numbers », (consulté le )
- Gary C. Kessler, « File signatures Table », (consulté le )
- (en) Robert C Martin, Clean Code : A handbook of agile software craftsmanship, Boston, Prentice Hall, , 300 p. (ISBN 978-0-13-235088-4 et 0-13-235088-2, OCLC 223933035), Chapitre 17 : Smells and Heuristics - G25 Replace Magic Numbers with Named Constants
- (en) Robert C Martin, Clean Code : A handbook of agile software craftsmanship, Boston, Prentice Hall, , 300 p. (ISBN 978-0-13-235088-4 et 0-13-235088-2, OCLC 223933035), Chapitre 17 : Smells and Heuristics - G16 Obscured Intent
- (en) Jeff Vogel, « Six ways to write more comprehensible code », (consulté le )
- (en) Joseph M. Newcomer, « Message Management - Guaranteeing uniqueness », (consulté le )
- NdT : notamment en anglais ; ici, si la constante AVARIE (avarié) existait en hexadécimal, l'exemple donnerait : « Ton programme est AVARIE ».
Annexes
modifierArticles connexes
modifierLien externe
modifier- (en) PNG (Portable Network Graphics) Specification, le chapitre sur la signature des fichiers PNG.