En programación informática , un número mágico es cualquiera de los siguientes:
El término número mágico o constante mágica se refiere al antipatrón de usar números directamente en el código fuente. Esto se ha considerado como una violación de una de las reglas más antiguas de la programación, que se remonta a los manuales COBOL , FORTRAN y PL/1 de la década de 1960. [1] El uso de números mágicos sin nombre en el código oscurece la intención de los desarrolladores al elegir ese número, [2] aumenta las oportunidades de errores sutiles (por ejemplo, ¿es correcto cada dígito en 3.14159265358979323846 y se puede redondear a 3.14159? [3] ) y hace que sea más difícil que el programa se adapte y amplíe en el futuro. [4] Reemplazar todos los números mágicos significativos con constantes con nombre (también llamadas variables explicativas) hace que los programas sean más fáciles de leer, comprender y mantener. [5]
Los nombres elegidos para que tengan significado en el contexto del programa pueden dar como resultado un código que sea más fácil de entender para un mantenedor que no sea el autor original (o incluso para el autor original después de un período de tiempo). [6] Un ejemplo de una constante con un nombre poco informativo es int SIXTEEN = 16
, mientras que int NUMBER_OF_BITS = 16
es más descriptivo.
Los problemas asociados con los 'números' mágicos descritos anteriormente no se limitan a los tipos numéricos y el término también se aplica a otros tipos de datos donde declarar una constante nombrada sería más flexible y comunicativo. [1] Por lo tanto, declarar const string testUserName = "John"
es mejor que varias apariciones del 'valor mágico' "John"
en un conjunto de pruebas .
Por ejemplo, si se requiere mezclar aleatoriamente los valores de una matriz que representa una baraja de cartas estándar , este pseudocódigo hace el trabajo utilizando el algoritmo de mezcla de Fisher-Yates :
para i del 1 al 52 j := i + int aleatorio (53 - i) - 1 a.swapEntries(i, j)
donde a
es un objeto de matriz, la función randomInt(x)
elige un entero aleatorio entre 1 y x , ambos inclusive, e swapEntries(i, j)
intercambia las entradas i y j de la matriz. En el ejemplo anterior, 52
es un número mágico. Se considera que un mejor estilo de programación es escribir lo siguiente:
int deckSize:= 52 para i desde 1 hasta deckSize j := i + randomInt(tamañoBaraja + 1 - i) - 1 a.swapEntries(i, j)
Esto es preferible por varias razones:
deckSize
variable en el segundo ejemplo sería un cambio simple de una línea.dekSize
" en lugar de " deckSize
" daría como resultado la advertencia del compilador de que dekSize
no se ha declarado.deckSize
en un parámetro de dicho procedimiento, mientras que el primer ejemplo requeriría varios cambios.función shuffle ( int deckSize) para i desde 1 hasta deckSize j := i + randomInt(tamañoBaraja + 1 - i) - 1 a.swapEntries(i, j)
Las desventajas son:
deckSize + 1
en tiempo de ejecución que el valor "53", aunque la mayoría de los compiladores e intérpretes modernos notarán que deckSize
se ha declarado como una constante y calcularán previamente el valor 53 en el código compilado. Incluso cuando eso no sea una opción, la optimización del bucle moverá la adición para que se realice antes del bucle. Por lo tanto, normalmente no hay ninguna penalización de velocidad (o esta es insignificante) en comparación con el uso de números mágicos en el código. Especialmente el costo de la depuración y el tiempo necesario para tratar de comprender el código no explicativo deben compararse con el pequeño costo de cálculo.En algunos contextos, el uso de constantes numéricas sin nombre es generalmente aceptado (y posiblemente "no sea mágico"). Si bien dicha aceptación es subjetiva y a menudo depende de los hábitos de codificación individuales, los siguientes son ejemplos comunes:
for (int i = 0; i < max; i += 1)
isEven = (x % 2 == 0)
, donde %
es el operador módulocircumference = 2 * Math.PI * radius
, [1] o para calcular el discriminante de una ecuación cuadrática comod = b^2 − 4*a*c
(f(x) ** 2 + f(y) ** 2) ** 0.5
paraLas constantes 1 y 0 se utilizan a veces para representar los valores booleanos true y false en lenguajes de programación sin un tipo booleano, como las versiones anteriores de C. La mayoría de los lenguajes de programación modernos proporcionan un tipo primitivoboolean
o , por lo que el uso de 0 y 1 no es recomendable. Esto puede ser más confuso ya que 0 a veces significa éxito programático (cuando -1 significa fracaso) y fracaso en otros casos (cuando 1 significa éxito).bool
En C y C++, 0 representa el puntero nulo . Al igual que con los valores booleanos, la biblioteca estándar de C incluye una definición de macro NULL
cuyo uso se recomienda. Otros lenguajes proporcionan un valor null
or específico nil
y, cuando este es el caso, no se debe utilizar ninguna alternativa. La constante de puntero tipado nullptr
se introdujo con C++11.
Los indicadores de formato se utilizaron por primera vez en el código fuente de Unix de la versión 7. [ cita requerida ]
Unix fue portado a uno de los primeros DEC PDP-11 /20s, que no tenía protección de memoria . Por lo tanto, las primeras versiones de Unix usaban el modelo de referencia de memoria reubicable . [7] Las versiones de Unix anteriores a la Sexta Edición leían un archivo ejecutable en la memoria y saltaban a la primera dirección de memoria baja del programa, la dirección relativa cero. Con el desarrollo de versiones paginadas de Unix, se creó un encabezado para describir los componentes de la imagen ejecutable . Además, se insertó una instrucción de bifurcación como la primera palabra del encabezado para omitir el encabezado e iniciar el programa. De esta manera, un programa podía ejecutarse en el antiguo modo de referencia de memoria reubicable (regular) o en modo paginado. A medida que se desarrollaron más formatos ejecutables, se agregaron nuevas constantes incrementando el desplazamiento de la bifurcación . [8]
En el código fuente de la sexta edición del cargador de programas de Unix, la función exec() leía la imagen ejecutable ( binaria ) del sistema de archivos. Los primeros 8 bytes del archivo eran un encabezado que contenía los tamaños del programa (texto) y las áreas de datos inicializadas (globales). Además, la primera palabra de 16 bits del encabezado se comparaba con dos constantes para determinar si la imagen ejecutable contenía referencias de memoria reubicables (normal), la imagen ejecutable de solo lectura paginada recientemente implementada o la imagen paginada de instrucciones y datos separados. [9] No se mencionaba el papel dual de la constante del encabezado, pero el byte de orden superior de la constante era, de hecho, el código de operación para la instrucción de bifurcación PDP-11 ( octal 000407 o hexadecimal 0107). Agregar siete al contador del programa mostraba que si se ejecutaba esta constante , ramificaría el servicio exec() de Unix sobre el encabezado de ocho bytes de la imagen ejecutable e iniciaría el programa.
Dado que la sexta y séptima ediciones de Unix empleaban código de paginación, el doble papel de la constante de encabezado estaba oculto. Es decir, el servicio exec() leía los datos de encabezado del archivo ejecutable ( meta ) en un búfer de espacio del núcleo , pero leía la imagen ejecutable en el espacio del usuario , por lo que no utilizaba la característica de ramificación de la constante. La creación de números mágicos se implementó en el enlazador y cargador de Unix y la ramificación de números mágicos probablemente todavía se usaba en el conjunto de programas de diagnóstico independientes que venían con la sexta y séptima ediciones. Por lo tanto, la constante de encabezado proporcionaba una ilusión y cumplía los criterios para ser mágica .
En la versión siete de Unix, la constante de encabezado no se probaba directamente, sino que se asignaba a una variable denominada ux_mag [10] y, posteriormente, se la denominaba número mágico . Probablemente debido a su singularidad, el término número mágico pasó a significar tipo de formato ejecutable, luego se expandió para significar tipo de sistema de archivos y se expandió nuevamente para significar cualquier tipo de archivo.
Los números mágicos son comunes en programas de muchos sistemas operativos. Los números mágicos implementan datos fuertemente tipados y son una forma de señalización en banda para el programa de control que lee los tipos de datos en tiempo de ejecución del programa. Muchos archivos tienen constantes de este tipo que identifican los datos que contienen. Detectar dichas constantes en los archivos es una forma simple y efectiva de distinguir entre muchos formatos de archivo y puede brindar más información en tiempo de ejecución .
CAFEBABE
. Cuando se comprimen con Pack200, los bytes se cambian a CAFED00D
.47
49
46
38
39
61
para "GIF89a" ( ) o "GIF87a" ( 47
49
46
38
37
61
)FF
D8
y terminan con FF
D9
. Los archivos JPEG/ JFIF contienen la cadena terminada en cero "JFIF" ( 4A
46
49
46
00
). Los archivos JPEG/ Exif contienen la cadena terminada en cero "Exif" ( 45
78
69
66
00
), seguida de más metadatos sobre el archivo.89
50
4E
47
0D
0A
1A
0A
que identifica el archivo como un archivo PNG y permite la detección de problemas comunes de transferencia de archivos: "\211PNG\r\n\032\n" ( ). Esa firma contiene varios caracteres de nueva línea para permitir la detección de conversiones de nueva línea automáticas no justificadas, como la transferencia del archivo mediante FTP con el modo de transferencia ASCII en lugar del modo binario . [11]4D
54
68
64
23
21
) seguido de la ruta a un intérprete , si es probable que el intérprete sea diferente de aquel desde el que se invocó el script.7F
seguido de "ELF" ( 7F
45
4C
46
).25
21
).25
50
44
46
).4D
5A
), las iniciales del diseñador del formato de archivo, Mark Zbikowski . La definición también permite el poco común "ZM" ( 5A
4D
) para dosZMXP, un EXE no PE. [12]19
54
01
19
o 01
19
54
según la versión; ambos representan el cumpleaños del autor, Marshall Kirk McKusick .55
AA
4A
6F
79
21
para "Joy!" ( ) como prefijo.49
49
2A
00
. "MM" es para Motorola, que utiliza orden de bytes big endian , por lo que el número mágico es 4D
4D
00
2A
.FE
FF
para big endian y FF
FE
para little endian). Y en Microsoft Windows , los archivos de texto UTF-8 suelen comenzar con la codificación UTF-8 del mismo carácter EF
BB
BF
.42
43
).D0
CF
11
E0
) comienzan con , lo que visualmente sugiere la palabra "DOCFILE0".50
4B
03
04
), donde "PK" son las iniciales de Phil Katz , autor de la utilidad de compresión DOS PKZIP .37
7A
BC
AF
27
1C
).El programa de utilidad de Unix file
puede leer e interpretar números mágicos de archivos, y el archivo que se utiliza para analizar la información se llama magic . La utilidad de Windows TrID tiene una finalidad similar.
2A
.52
46
42
, por "Remote Frame Buffer") seguido del número de versión del protocolo del cliente.FF
53
4D
42
', o "\xFFSMB"
al inicio de la solicitud SMB.05
al inicio de la solicitud (que representa Microsoft DCE/RPC versión 5), seguido inmediatamente por un 00
o 01
para la versión secundaria. En las solicitudes MSRPC basadas en UDP, el primer byte siempre es 04
.4D
45
4F
57
). Las extensiones de depuración (usadas para el enlace de canales DCOM) están precedidas por la secuencia de bytes "MARB" ( 4D
41
52
42
).19
que representa la longitud del encabezado, seguido inmediatamente por la frase "protocolo BitTorrent" en la posición de byte 1.E3
representa un cliente eDonkey, C5
representa eMule y D4
representa eMule comprimido.0xD9B4BEF9
que indica la red principal, mientras que la constante 0xDAB5BFFA
indica la red de prueba.80
y una respuesta del servidor SSLv3 a un saludo del cliente comienza con 16
(aunque esto puede variar).0x63
0x82
0x53
0x63
" al comienzo de la sección de opciones del paquete. Este valor se incluye en todos los tipos de paquetes DHCP.0x505249202a20485454502f322e300d0a0d0a534d0d0a0d0a
' o " PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n
". El prefacio está diseñado para evitar el procesamiento de tramas por parte de servidores e intermediarios que admiten versiones anteriores de HTTP pero no la 2.0.Los números mágicos son comunes en las funciones e interfaces de API en muchos sistemas operativos , incluidos DOS , Windows y NetWare :
0000
para 1234
decidir si el sistema debe contar la memoria o no al reiniciar, realizando así un arranque en frío o en caliente. Estos valores también son utilizados por los administradores de memoria EMM386 que interceptan las solicitudes de arranque. [13] Las BIOS también utilizan valores mágicos 55 AA
para determinar si un disco es arrancable. [14]Esta es una lista de límites de los tipos de almacenamiento de datos: [16]
Es posible crear o alterar identificadores únicos globales (GUID) para que sean memorables, pero esto se desaconseja enfáticamente ya que compromete su fortaleza como identificadores casi únicos. [17] [18] Las especificaciones para generar GUID y UUID son bastante complejas, lo que lleva a que sean virtualmente únicos, si se implementan correctamente. [19]
Los números de identificación de producto de Microsoft Windows para productos de Microsoft Office a veces terminan con 0000-0000-0000000FF1CE
("OFFICE"), como { 90160000-008C-0000-0000-0000000FF1CE
}, el identificador de producto para el "Componente de extensibilidad de hacer clic y ejecutar de Office 16".
Java utiliza varios GUID que comienzan con CAFEEFAC
. [20]
En la tabla de particiones GUID del esquema de particionamiento GPT, las particiones de arranque del BIOS utilizan el GUID especial { 21686148-6449-6E6F-744E-656564454649
} [21] que no sigue la definición de GUID; en su lugar, se forma utilizando los códigos ASCIIHah!IdontNeedEFI
para la cadena " " parcialmente en orden little endian . [22]
Los valores de depuración mágicos son valores específicos que se escriben en la memoria durante la asignación o desasignación, de modo que más tarde sea posible saber si se han corrompido o no, y para que sea obvio cuando se están utilizando valores tomados de la memoria no inicializada. La memoria suele verse en hexadecimal, por lo que es común recordar valores repetidos o en lenguaje hexadecimal . Se pueden preferir valores numéricamente impares para que los procesadores sin direccionamiento de bytes fallen al intentar usarlos como punteros (que deben caer en direcciones pares). Se deben elegir valores que estén lejos de las direcciones probables (el código del programa, los datos estáticos, los datos del montón o la pila). De manera similar, se pueden elegir de modo que no sean códigos válidos en el conjunto de instrucciones para la arquitectura dada.
Dado que es muy poco probable, aunque posible, que un entero de 32 bits tome este valor específico, la aparición de dicho número en un depurador o en un volcado de memoria probablemente indique un error, como un desbordamiento de búfer o una variable no inicializada .
Algunos ejemplos famosos y comunes incluyen:
La mayoría de estos tienen una longitud de 32 bits : el tamaño de palabra de la mayoría de las computadoras con arquitectura de 32 bits.
La prevalencia de estos valores en la tecnología de Microsoft no es casualidad; se analizan en detalle en el libro de Steve Maguire Writing Solid Code de Microsoft Press . El autor ofrece una variedad de criterios para estos valores, como:
Dado que a menudo se utilizaban para marcar áreas de la memoria que estaban esencialmente vacías, algunos de estos términos llegaron a utilizarse en frases que significaban "desaparecido, abortado, borrado de la memoria"; por ejemplo, "Su programa está MUERTO". [ cita requerida ]