Nombre magique (programmation)

Un article de Wikipédia, l'encyclopédie libre.
Aller à : navigation, rechercher
Page d'aide sur l'homonymie Pour les articles homonymes, voir Nombre magique.

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[modifier | modifier le code]

Origine[modifier | modifier le code]

Ce 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[modifier | modifier le code]

Plusieurs 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 | modifier le code]

  • 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), le code se transforme en CAFED00D.
  • Les images gif utilisent le code ASCII GIF89a (47 49 46 38 39 61) ou GIF87a (47 49 46 38 37 61).
  • Les images JPEG commencent par FF D8 et finissent par FF D9. Les images JPEG/JFIF contiennent le code ASCII pour JFIF (4A 46 49 46) et se terminent par une chaîne de caractères vide. Les images JPEG/Exif contiennent le code ASCII pour Exif (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 code ZM (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 ou 01 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 vers exec.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 ou MM 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 code 49 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 une marque d'ordre des octets pour détecter l'endianness (FE FF pour les big-endian et FF FE pour les little-endian). Les fichiers UTF-8 commencent le plus souvent par EF 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) ou WAD3 (Quake II et dérivés).

Détection[modifier | modifier le code]

Sous 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 | modifier le code]

  • Le protocole OSCAR (en) 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 par 00 ou 01 pour le numéro de version mineur. Les requêtes UDP commencent toujours par 04.
  • Les interfaces COM et DCOM sérialisées comme OBJREF (en) commencent toujours par « MEOW » (4D 45 4F 57). Les extensions de debugage utilisées par DCOM commencent par « MARB » (4D 41 52 42).
  • Une requête non-cryptée d'un trackeur Bittorrent (en) 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 et D4 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 par 80 et la réponse d'un serveur version 3 à un client commence par 16 (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[modifier | modifier le code]

Le 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 tous 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 = 32 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)

jeu est un objet de type tableau, la fonction randomInt(x) choisit un chiffre 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 nombreDeCarte := 52
  for i from 1 to nombreDeCarte
     j := i + randomInt(nombreDeCarte + 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 nombreDeCarte 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 nombreDeCarte en paramètre de la procédure :
  function melange(int nombreDeCarte )
     for i from 1 to nombreDeCarte 
        j := i + randomInt(nombreDeCarte + 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 « nombreDeCate » au lieu de « nombreDeCarte » lèvera un warning (ou une erreur) du compilateur informant que « nombreDeCate » 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 nombreDeCarte + 1 prend plus de temps que l'expression 53. Néanmoins, la plupart des compilateurs et des interpréteurs modernes sont capables de comprendre que la variable nombreDeCarte 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 debbugage peut être plus difficile si le débuggeur ne fait pas la correspondance entre les noms de variable et leur valeur.

Usages acceptés comme constantes numériques non-nommées[modifier | modifier le code]

Dans 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émentation i++ 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 « Vraie » 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 pointer 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[modifier | modifier le code]

Il 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[modifier | modifier le code]

Certains « 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.

Valeurs classiques
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 debuggeur eVC quand la connexion avec le débuggeur 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 (en)
C0DEDBAD Utilisé pour le débuggage 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 debugage
CCCCCCCC Utilisé par la libraire de debugage du C++ de Microsoft pour repérer la mémoire stack non-initialisée
CDCDCDCD Utilisé par la libraire de debugage 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ébuggeur 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ébuggeur 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 :

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 | modifier le code]

  1. (en) The Linux Information Project, « Magic Number Definition »,‎ 21 août 2006 (consulté le 19 novembre 2009)
  2. a et b Odd Comments and Strange Doings in Unix « http://cm.bell-labs.com/cm/cs/who/dmr/odd.html » (ArchiveWikiwixArchive.isGoogleQue faire ?). Consulté le 2013-11-07
  3. Personal communication with Dennis M. Ritchie
  4. Version six system1 source file [1]
  5. Version seven system1 source file [2]
  6. (en) Gary C. Kessler, « Linux Magic Numbers » (consulté le 19 novembre 2009)
  7. (en) Richard Ogley, « File magic numbers »,‎ 17 février 2003 (consulté le 19 novembre 2009)
  8. Gary C. Kessler, « File signatures Table »,‎ 10 juin 2009 (consulté le 19 novembre 2009)
  9. a et b (en) Robert C Martin, Clean Code : A handbook of agile software craftsmanship, Boston, Prentice Hall,‎ 2009, 300 p. (ISBN 0-13-235088-2, OCLC 223933035), p. Chapitre 17 : Smells and Heuristics - G25 Replace Magic Numbers with Named Constants
  10. (en) Robert C Martin, Clean Code : A handbook of agile software craftsmanship, Boston, Prentice Hall,‎ 2009, 300 p. (ISBN 0-13-235088-2, OCLC 223933035), p. Chapitre 17 : Smells and Heuristics - G16 Obscured Intent
  11. (en) Jeff Vogel, « Six ways to write more comprehensible code »,‎ 29 mai 2007 (consulté le 19 novembre 2009)
  12. (en) Joseph M. Newcomer, « Message Management - Guaranteeing uniqueness »,‎ 13 octobre 2001 (consulté le 6 septembre 2009)
  13. NdT: notamment en anglais ; ici, si la constante AVARIE (avarié) existait en hexadécimal, l'exemple donnerait : « Ton programme est AVARIE ».

Annexes[modifier | modifier le code]

Articles connexes[modifier | modifier le code]

  • NaN (Not a Number), une catégorie de nombres magiques
  • Hexspeak

Lien externe[modifier | modifier le code]