En programación de computadoras , el lenguaje ensamblador (alternativamente lenguaje ensamblador [1] o código de máquina simbólico ), [2] [3] [4] a menudo denominado simplemente ensamblador y comúnmente abreviado como ASM o asm , es cualquier lenguaje de programación de bajo nivel con una correspondencia muy fuerte entre las instrucciones del lenguaje y las instrucciones del código de máquina de la arquitectura . [5] El lenguaje ensamblador generalmente tiene una declaración por instrucción de máquina (1:1), pero las constantes, comentarios , directivas de ensamblador , [6] etiquetas simbólicas de, por ejemplo, ubicaciones de memoria , registros y macros [7] [1] generalmente son también apoyado.
El primer código ensamblador en el que se utiliza un lenguaje para representar instrucciones de código de máquina se encuentra en el trabajo de 1947 de Kathleen y Andrew Donald Booth , Coding for ARC . [8] El código ensamblador se convierte en código de máquina ejecutable mediante un programa de utilidad denominado ensamblador . El término "ensamblador" se atribuye generalmente a Wilkes , Wheeler y Gill en su libro de 1951 The Preparation of Programs for an Electronic Digital Computer , [9] quienes, sin embargo, usaron el término para significar "un programa que ensambla otro programa que consta de varios secciones en un solo programa". [10] El proceso de conversión se conoce como ensamblaje , como ensamblar el código fuente . El paso computacional cuando un ensamblador está procesando un programa se llama tiempo de ensamblaje .
Debido a que el ensamblador depende de las instrucciones del código de máquina, cada lenguaje ensamblador [nb 1] es específico de una arquitectura de computadora en particular . [11] [12] [13]
A veces hay más de un ensamblador para la misma arquitectura y, a veces, un ensamblador es específico de un sistema operativo o de sistemas operativos concretos. La mayoría de los lenguajes ensambladores no proporcionan una sintaxis específica para las llamadas al sistema operativo, y la mayoría de los lenguajes ensambladores se pueden usar universalmente con cualquier sistema operativo, [nb 2] ya que el lenguaje proporciona acceso a todas las capacidades reales del procesador , sobre las cuales se basan todos los mecanismos de llamadas del sistema. en definitiva descansar. A diferencia de los lenguajes ensambladores, la mayoría de los lenguajes de programación de alto nivel son generalmente portátiles en múltiples arquitecturas, pero requieren interpretación o compilación , tareas mucho más complicadas que el ensamblaje.
En las primeras décadas de la informática, era común que tanto la programación de sistemas como la programación de aplicaciones se realizaran completamente en lenguaje ensamblador. Si bien sigue siendo irremplazable para algunos propósitos, la mayor parte de la programación ahora se realiza en lenguajes compilados e interpretados de alto nivel. En " No Silver Bullet ", Fred Brooks resumió los efectos del abandono de la programación en lenguaje ensamblador: "Seguramente el golpe más poderoso para la productividad, confiabilidad y simplicidad del software ha sido el uso progresivo de lenguajes de alto nivel para la programación. La mayoría de los observadores "A ese desarrollo se le atribuye al menos un factor de cinco en productividad, y con ganancias concomitantes en confiabilidad, simplicidad y comprensibilidad". [14]
Hoy en día, es típico utilizar pequeñas cantidades de código en lenguaje ensamblador dentro de sistemas más grandes implementados en un lenguaje de nivel superior, por razones de rendimiento o para interactuar directamente con el hardware de maneras no soportadas por el lenguaje de nivel superior. Por ejemplo, poco menos del 2% de la versión 4.9 del código fuente del kernel de Linux está escrito en ensamblador; más del 97% está escrito en C. [15]
El lenguaje ensamblador utiliza un mnemotécnico para representar, por ejemplo, cada instrucción de máquina de bajo nivel o código de operación , cada directiva , normalmente también cada registro arquitectónico , bandera , etc. Algunos de los mnemotécnicos pueden estar integrados y otros definidos por el usuario. Muchas operaciones requieren uno o más operandos para formar una instrucción completa. La mayoría de los ensambladores permiten constantes, registros y etiquetas con nombre para ubicaciones de programas y memoria, y pueden calcular expresiones para operandos. De este modo, los programadores se liberan de tediosos cálculos repetitivos y los programas ensambladores son mucho más legibles que el código de máquina. Dependiendo de la arquitectura, estos elementos también se pueden combinar para instrucciones específicas o modos de direccionamiento utilizando compensaciones u otros datos, así como direcciones fijas. Muchos ensambladores ofrecen mecanismos adicionales para facilitar el desarrollo de programas, controlar el proceso de ensamblaje y ayudar en la depuración .
Algunos están orientados a columnas, con campos específicos en columnas específicas; Esto era muy común en las máquinas que utilizaban tarjetas perforadas en los años cincuenta y principios de los sesenta. Algunos ensambladores tienen sintaxis de forma libre, con campos separados por delimitadores, por ejemplo, puntuación, espacios en blanco . Algunos ensambladores son híbridos, con, por ejemplo, etiquetas en una columna específica y otros campos separados por delimitadores; esto se volvió más común que la sintaxis orientada a columnas en la década de 1960.
Un programa ensamblador crea código objeto traduciendo combinaciones de mnemónicos y sintaxis para operaciones y modos de direccionamiento a sus equivalentes numéricos. Esta representación normalmente incluye un código de operación (" código de operación ") así como otros bits y datos de control. El ensamblador también calcula expresiones constantes y resuelve nombres simbólicos para ubicaciones de memoria y otras entidades. [20] El uso de referencias simbólicas es una característica clave de los ensambladores, ya que ahorra cálculos tediosos y actualizaciones manuales de direcciones después de modificaciones del programa. La mayoría de los ensambladores también incluyen funciones de macro para realizar sustitución textual, por ejemplo, para generar secuencias cortas comunes de instrucciones en línea , en lugar de llamadas subrutinas .
Algunos ensambladores también pueden realizar algunos tipos simples de optimizaciones específicas del conjunto de instrucciones . Un ejemplo concreto de esto pueden ser los omnipresentes ensambladores x86 de varios proveedores. Llamado jump-sizing , [20] la mayoría de ellos son capaces de realizar reemplazos de instrucciones de salto (saltos largos reemplazados por saltos cortos o relativos) en cualquier número de pases, previa solicitud. Otros pueden incluso realizar una simple reorganización o inserción de instrucciones, como algunos ensambladores para arquitecturas RISC que pueden ayudar a optimizar una programación de instrucciones sensata para explotar la canalización de la CPU de la manera más eficiente posible. [21]
Los ensambladores han estado disponibles desde la década de 1950, como el primer paso por encima del lenguaje de máquina y antes de los lenguajes de programación de alto nivel como Fortran , Algol , COBOL y Lisp . También ha habido varias clases de traductores y generadores de código semiautomáticos con propiedades similares a los lenguajes ensambladores y de alto nivel, siendo Speedcode quizás uno de los ejemplos más conocidos.
Puede haber varios ensambladores con diferente sintaxis para una CPU o arquitectura de conjunto de instrucciones en particular . Por ejemplo, una instrucción para agregar datos de memoria a un registro en un procesador de la familia x86 podría ser , en la sintaxisadd eax,[ebx]
original de Intel , mientras que esto estaría escrito en la sintaxis de AT&T utilizada por el ensamblador GNU . A pesar de las diferentes apariencias, diferentes formas sintácticas generalmente generan el mismo código de máquina numérico . Un único ensamblador también puede tener diferentes modos para admitir variaciones en las formas sintácticas, así como sus interpretaciones semánticas exactas (como la sintaxis FASM , la sintaxis TASM , el modo ideal, etc., en el caso especial de la programación ensambladora x86 ).addl (%ebx),%eax
Hay dos tipos de ensambladores según la cantidad de pasos necesarios a través del código fuente (cuántas veces el ensamblador lee el código fuente) para producir el archivo objeto.
En ambos casos, el ensamblador debe poder determinar el tamaño de cada instrucción en las pasadas iniciales para poder calcular las direcciones de los símbolos posteriores. Esto significa que si el tamaño de una operación que hace referencia a un operando definido posteriormente depende del tipo o la distancia del operando, el ensamblador hará una estimación pesimista cuando encuentre la operación por primera vez y, si es necesario, la rellenará con uno o más " no ". -operación " instrucciones en un pase posterior o la errata. En un ensamblador con optimización de mirilla , las direcciones se pueden recalcular entre pasadas para permitir reemplazar el código pesimista con código adaptado a la distancia exacta del objetivo.
La razón original para el uso de ensambladores de una sola pasada fue el tamaño de la memoria y la velocidad de ensamblaje; a menudo, una segunda pasada requeriría almacenar la tabla de símbolos en la memoria (para manejar referencias directas ), rebobinar y releer el código fuente del programa en cinta , o releer un baraja de cartas o cinta de papel perforada . Las computadoras posteriores con memorias mucho más grandes (especialmente almacenamiento en disco) tenían el espacio para realizar todo el procesamiento necesario sin dicha relectura. La ventaja del ensamblador multipaso es que la ausencia de erratas hace que el proceso de vinculación (o la carga del programa si el ensamblador produce directamente código ejecutable) sea más rápido. [22]
Ejemplo: en el siguiente fragmento de código, un ensamblador de una sola pasada podría determinar la dirección de la referencia hacia atrás BKWD al ensamblar la declaración S2 , pero no podría determinar la dirección de la referencia directa FWD al ensamblar la declaración de rama S1. ; de hecho, FWD puede no estar definido. Un ensamblador de dos pasos determinaría ambas direcciones en el paso 1, por lo que se conocerían al generar código en el paso 2.
S1 B FWD ...EQU FWD * ...EQU. BKWD * ...S2 B BKWD
Los ensambladores de alto nivel más sofisticados proporcionan abstracciones de lenguaje como:
Consulte Diseño de lenguaje a continuación para obtener más detalles.
Un programa escrito en lenguaje ensamblador consta de una serie de instrucciones mnemónicas del procesador y metadeclaraciones (conocidas como operaciones declarativas, directivas, pseudoinstrucciones, pseudooperaciones y pseudooperaciones), comentarios y datos. Las instrucciones en lenguaje ensamblador generalmente constan de un mnemotécnico de código de operación seguido de un operando , que puede ser una lista de datos, argumentos o parámetros. [24] Algunas instrucciones pueden estar "implícitas", lo que significa que los datos sobre los cuales opera la instrucción están definidos implícitamente por la instrucción misma; dicha instrucción no requiere un operando. La declaración resultante es traducida por un ensamblador a instrucciones en lenguaje de máquina que pueden cargarse en la memoria y ejecutarse.
Por ejemplo, la siguiente instrucción le indica a un procesador x86 / IA-32 que mueva un valor inmediato de 8 bits a un registro . El código binario para esta instrucción es 10110 seguido de un identificador de 3 bits para qué registro utilizar. El identificador del registro AL es 000, por lo que el siguiente código de máquina carga el registro AL con los datos 01100001. [24]
10110000 01100001
Este código informático binario se puede hacer más legible para los humanos expresándolo en hexadecimal de la siguiente manera.
B0 61
Aquí, B0
significa "Mover una copia del siguiente valor a AL " y 61
es una representación hexadecimal del valor 01100001, que es 97 en decimal . El lenguaje ensamblador para la familia 8086 proporciona el mnemotécnico MOV (una abreviatura de mover ) para instrucciones como esta, por lo que el código de máquina anterior se puede escribir de la siguiente manera en lenguaje ensamblador, completo con un comentario explicativo si es necesario, después del punto y coma. Esto es mucho más fácil de leer y recordar.
MOVAL , 61h ; Cargue AL con 97 decimales (61 hexadecimales)
En algunos lenguajes ensambladores (incluido éste), se puede utilizar el mismo mnemotécnico, como MOV, para una familia de instrucciones relacionadas para cargar, copiar y mover datos, ya sean valores inmediatos, valores en registros o ubicaciones de memoria apuntadas por valores en registros o por direcciones inmediatas (también conocidas como directas). Otros ensambladores pueden usar mnemónicos de código de operación separados, como L para "mover memoria a registro", ST para "mover registro a memoria", LR para "mover registro a registro", MVI para "mover operando inmediato a la memoria", etc.
Si se utiliza el mismo mnemónico para diferentes instrucciones, eso significa que el mnemónico corresponde a varios códigos de instrucción binaria diferentes, excluyendo los datos (por ejemplo, 61h
en este ejemplo), dependiendo de los operandos que siguen al mnemónico. Por ejemplo, para las CPU x86/IA-32, la sintaxis del lenguaje ensamblador Intel MOV AL, AH
representa una instrucción que mueve el contenido del registro AH al registro AL . La forma hexadecimal [nb 3] de esta instrucción es:
88 E0
El primer byte, 88h, identifica un movimiento entre un registro del tamaño de un byte y otro registro o memoria, y el segundo byte, E0h, está codificado (con tres campos de bits) para especificar que ambos operandos son registros, la fuente es AH. , y el destino es AL .
En un caso como este, donde el mismo mnemotécnico puede representar más de una instrucción binaria, el ensamblador determina qué instrucción generar examinando los operandos. En el primer ejemplo, el operando 61h
es una constante numérica hexadecimal válida y no es un nombre de registro válido, por lo que solo B0
se puede aplicar la instrucción. En el segundo ejemplo, el operando AH
es un nombre de registro válido y no una constante numérica válida (hexadecimal, decimal, octal o binaria), por lo que solo 88
se puede aplicar la instrucción.
Los lenguajes ensambladores siempre están diseñados para que este tipo de falta de ambigüedad se aplique universalmente mediante su sintaxis. Por ejemplo, en el lenguaje ensamblador Intel x86, una constante hexadecimal debe comenzar con un dígito numérico, de modo que el número hexadecimal 'A' (igual al diez decimal) se escriba como 0Ah
o 0AH
no AH
, específicamente para que no parezca ser el nombre del registro AH . (La misma regla también evita la ambigüedad con los nombres de los registros BH , CH y DH , así como con cualquier símbolo definido por el usuario que termine con la letra H y que de otro modo contenga solo caracteres que sean dígitos hexadecimales, como la palabra "PLAYA ".)
Volviendo al ejemplo original, mientras el código de operación x86 10110000 ( B0
) copia un valor de 8 bits en el registro AL , 10110001 ( B1
) lo mueve a CL y 10110010 ( B2
) lo hace a DL . A continuación se muestran ejemplos de lenguaje ensamblador para estos. [24]
MOVAL , 1h ; Carga AL con valor inmediato 1 MOV CL , 2h ; Carga CL con valor inmediato 2 MOV DL , 3h ; Cargar DL con valor inmediato 3
La sintaxis de MOV también puede ser más compleja, como muestran los siguientes ejemplos. [25]
MOV EAX , [ EBX ] ; Mueva los 4 bytes de la memoria en la dirección contenida en EBX a EAX MOV [ ESI + EAX ], CL ; Mueva el contenido de CL al byte en la dirección ESI+EAX MOV DS , DX ; Mover el contenido de DX al registro de segmento DS
En cada caso, el mnemotécnico MOV se traduce directamente a uno de los códigos de operación 88-8C, 8E, A0-A3, B0-BF, C6 o C7 por un ensamblador, y el programador normalmente no tiene que saber ni recordar cuál. [24]
Transformar el lenguaje ensamblador en código de máquina es trabajo de un ensamblador, y lo contrario puede lograrse, al menos parcialmente, mediante un desensamblador . A diferencia de los lenguajes de alto nivel , existe una correspondencia uno a uno entre muchas declaraciones ensambladoras simples e instrucciones en lenguaje de máquina. Sin embargo, en algunos casos, un ensamblador puede proporcionar pseudoinstrucciones (esencialmente macros) que se expanden a varias instrucciones en lenguaje de máquina para proporcionar la funcionalidad comúnmente necesaria. Por ejemplo, para una máquina que carece de una instrucción "bifurcarse si es mayor o igual", un ensamblador puede proporcionar una pseudoinstrucción que se expanda a las instrucciones "establecer si es menor que" y "bifurcarse si es cero (según el resultado de la instrucción establecer)" de la máquina. . La mayoría de los ensambladores con todas las funciones también proporcionan un rico lenguaje de macros (que se analiza a continuación) que utilizan los proveedores y programadores para generar código y secuencias de datos más complejos. Dado que la información sobre pseudoinstrucciones y macros definidas en el entorno ensamblador no está presente en el programa objeto, un desensamblador no puede reconstruir las invocaciones de macros y pseudoinstrucciones, sino que solo puede desensamblar las instrucciones reales de la máquina que el ensamblador generó a partir de esas entidades abstractas del lenguaje ensamblador. Del mismo modo, dado que el ensamblador ignora los comentarios en el archivo fuente en lenguaje ensamblador y no tienen ningún efecto en el código objeto que genera, un desensamblador siempre es completamente incapaz de recuperar los comentarios fuente.
Cada arquitectura de computadora tiene su propio lenguaje de máquina. Las computadoras difieren en la cantidad y tipo de operaciones que admiten, en los diferentes tamaños y cantidades de registros y en las representaciones de los datos almacenados. Si bien la mayoría de las computadoras de uso general pueden realizar esencialmente la misma funcionalidad, las formas en que lo hacen difieren; los lenguajes ensambladores correspondientes reflejan estas diferencias.
Pueden existir múltiples conjuntos de mnemónicos o sintaxis de lenguaje ensamblador para un solo conjunto de instrucciones, generalmente instanciadas en diferentes programas ensambladores. En estos casos el más popular suele ser el que proporciona el fabricante de la CPU y se utiliza en su documentación.
Dos ejemplos de CPU que tienen dos conjuntos diferentes de mnemónicos son la familia Intel 8080 y la Intel 8086/8088. Debido a que Intel reclamó derechos de autor sobre sus mnemotécnicos en lenguaje ensamblador (al menos en cada página de su documentación publicada en los años 1970 y principios de los 80), algunas empresas que producían de forma independiente CPU compatibles con los conjuntos de instrucciones de Intel inventaron sus propios mnemotécnicos. La CPU Zilog Z80 , una mejora del Intel 8080A , admite todas las instrucciones del 8080A y muchas más; Zilog inventó un lenguaje ensamblador completamente nuevo, no sólo para las nuevas instrucciones sino también para todas las instrucciones del 8080A. Por ejemplo, donde Intel usa los mnemónicos MOV , MVI , LDA , STA , LXI , LDAX , STAX , LHLD y SHLD para varias instrucciones de transferencia de datos, el lenguaje ensamblador Z80 usa el mnemónico LD para todas ellas. Un caso similar son las CPU NEC V20 y V30 , copias mejoradas de Intel 8086 y 8088, respectivamente. Al igual que Zilog con el Z80, NEC inventó nuevos mnemotécnicos para todas las instrucciones 8086 y 8088, para evitar acusaciones de infracción de los derechos de autor de Intel. (Es cuestionable si dichos derechos de autor pueden ser válidos, y posteriormente compañías de CPU como AMD [nb 4] y Cyrix volvieron a publicar los mnemotécnicos de instrucciones x86/IA-32 de Intel exactamente sin permiso ni sanción legal.) Es dudoso que en la práctica muchas personas quién programó el V20 y el V30 en realidad escribió en el lenguaje ensamblador de NEC en lugar del de Intel; Dado que dos lenguajes ensambladores para la misma arquitectura de conjunto de instrucciones son isomórficos (algo así como el inglés y el latín Pig ), no es necesario utilizar el lenguaje ensamblador publicado por el propio fabricante con los productos de ese fabricante.
Existe un gran grado de diversidad en la forma en que los autores de ensambladores categorizan las declaraciones y en la nomenclatura que utilizan. En particular, algunos describen cualquier cosa que no sea una mnemónica de máquina o una mnemónica extendida como una pseudooperación (pseudo-op). Un lenguaje ensamblador típico consta de 3 tipos de instrucciones que se utilizan para definir las operaciones del programa:
Las instrucciones (declaraciones) en lenguaje ensamblador son generalmente muy simples, a diferencia de las de los lenguajes de alto nivel . Generalmente, un mnemónico es un nombre simbólico para una única instrucción ejecutable en lenguaje de máquina (un código de operación ), y hay al menos un mnemónico de código de operación definido para cada instrucción en lenguaje de máquina. Cada instrucción normalmente consta de una operación u código de operación más cero o más operandos . La mayoría de las instrucciones se refieren a un valor único o a un par de valores. Los operandos pueden ser inmediatos (valor codificado en la propia instrucción), registros especificados en la instrucción o implícitos, o direcciones de datos ubicados en otro lugar del almacenamiento. Esto está determinado por la arquitectura del procesador subyacente: el ensamblador simplemente refleja cómo funciona esta arquitectura. Los mnemotécnicos extendidos se usan a menudo para especificar una combinación de un código de operación con un operando específico, por ejemplo, los ensambladores de System/360 usan B
como un mnemotécnico extendido para BC
con una máscara de 15 y NOP
("SIN OPERACIÓN" – no hacer nada en un paso) para BC
con una máscara de 0.
Los mnemotécnicos extendidos se utilizan a menudo para respaldar usos especializados de instrucciones, a menudo con fines que no son obvios a partir del nombre de la instrucción. Por ejemplo, muchas CPU no tienen una instrucción NOP explícita, pero sí tienen instrucciones que pueden usarse para ese propósito. En las CPU 8086, la instrucción se usa para , siendo un pseudocódigo de operación para codificar la instrucción . Algunos desensambladores reconocen esto y decodificarán la instrucción como . De manera similar, los ensambladores de IBM para System/360 y System/370 utilizan los mnemónicos extendidos y para y con máscaras cero. Para la arquitectura SPARC, estas se conocen como instrucciones sintéticas . [26]xchg ax,ax
nop
nop
xchg ax,ax
xchg ax,ax
nop
NOP
NOPR
BC
BCR
Algunos ensambladores también admiten macroinstrucciones integradas simples que generan dos o más instrucciones de máquina. Por ejemplo, con algunos ensambladores Z80 ld hl,bc
se reconoce que la instrucción se genera ld l,c
seguida de ld h,b
. [27] A veces se les conoce como pseudocódigos de operación .
Los mnemotécnicos son símbolos arbitrarios; En 1985, el IEEE publicó el Estándar 694 para un conjunto uniforme de mnemotécnicos para ser utilizado por todos los ensambladores. Desde entonces, la norma ha sido retirada.
Hay instrucciones que se utilizan para definir elementos de datos que contengan datos y variables. Definen el tipo de datos, la longitud y la alineación de los datos. Estas instrucciones también pueden definir si los datos están disponibles para programas externos (programas ensamblados por separado) o solo para el programa en el que se define la sección de datos. Algunos ensambladores los clasifican como pseudooperaciones.
Las directivas de ensamblaje, también llamadas pseudocódigos de operación, pseudooperaciones o pseudooperaciones, son comandos dados a un ensamblador "que le indica que realice operaciones distintas a las instrucciones de ensamblaje". [20] Las directivas afectan el funcionamiento del ensamblador y "pueden afectar el código objeto, la tabla de símbolos, el archivo de listado y los valores de los parámetros internos del ensamblador". A veces, el término pseudocódigo de operación se reserva para directivas que generan código objeto, como las que generan datos. [28]
Los nombres de las pseudooperaciones suelen comenzar con un punto para distinguirlas de las instrucciones de la máquina. Las pseudooperaciones pueden hacer que el ensamblaje del programa dependa de los parámetros ingresados por un programador, de modo que un programa pueda ensamblarse de diferentes maneras, tal vez para diferentes aplicaciones. O bien, se puede utilizar una pseudooperación para manipular la presentación de un programa para facilitar su lectura y mantenimiento. Otro uso común de las pseudooperaciones es reservar áreas de almacenamiento para datos en tiempo de ejecución y, opcionalmente, inicializar su contenido con valores conocidos.
Los ensambladores simbólicos permiten a los programadores asociar nombres arbitrarios ( etiquetas o símbolos ) con ubicaciones de memoria y varias constantes. Por lo general, a cada constante y variable se le asigna un nombre para que las instrucciones puedan hacer referencia a esas ubicaciones por nombre, promoviendo así el código autodocumentado . En el código ejecutable, el nombre de cada subrutina está asociado con su punto de entrada, por lo que cualquier llamada a una subrutina puede usar su nombre. Dentro de las subrutinas, los destinos GOTO reciben etiquetas. Algunos ensambladores admiten símbolos locales que a menudo son léxicamente distintos de los símbolos normales (por ejemplo, el uso de "10$" como destino GOTO).
Algunos ensambladores, como NASM , brindan administración de símbolos flexible, lo que permite a los programadores administrar diferentes espacios de nombres , calcular automáticamente compensaciones dentro de estructuras de datos y asignar etiquetas que se refieren a valores literales o al resultado de cálculos simples realizados por el ensamblador. Las etiquetas también se pueden utilizar para inicializar constantes y variables con direcciones reubicables.
Los lenguajes ensambladores, como la mayoría de los otros lenguajes informáticos, permiten agregar comentarios al código fuente del programa que serán ignorados durante el ensamblado. Los comentarios juiciosos son esenciales en los programas en lenguaje ensamblador, ya que el significado y el propósito de una secuencia de instrucciones binarias de máquina pueden ser difíciles de determinar. El lenguaje ensamblador "sin formato" (sin comentarios) generado por compiladores o desensambladores es bastante difícil de leer cuando se deben realizar cambios.
Muchos ensambladores admiten macros predefinidas , y otros admiten macros definidas por el programador (y redefinibles repetidamente) que involucran secuencias de líneas de texto en las que se incrustan variables y constantes. La definición de macro suele ser [nb 5] una mezcla de declaraciones en ensamblador, por ejemplo, directivas, instrucciones simbólicas de máquina y plantillas para declaraciones en ensamblador. Esta secuencia de líneas de texto puede incluir códigos de operación o directivas. Una vez que se ha definido una macro, se puede usar su nombre en lugar de un mnemotécnico. Cuando el ensamblador procesa dicha declaración, reemplaza la declaración con las líneas de texto asociadas con esa macro, luego las procesa como si existieran en el archivo de código fuente (incluida, en algunos ensambladores, la expansión de cualquier macro existente en el texto de reemplazo). . Las macros en este sentido datan de los codificadores automáticos de IBM de la década de 1950. [29]
Los ensambladores de macros suelen tener directivas para, por ejemplo, definir macros, definir variables, establecer variables como resultado de una expresión aritmética, lógica o de cadena, iterar y generar código condicionalmente. Algunas de esas directivas pueden estar restringidas para su uso dentro de una definición de macro, por ejemplo, MEXIT en HLASM , mientras que otras pueden permitirse dentro de código abierto (fuera de definiciones de macro), por ejemplo, AIF y COPY en HLASM.
En lenguaje ensamblador, el término "macro" representa un concepto más completo que en otros contextos, como el preprocesador en el lenguaje de programación C , donde su directiva #define normalmente se usa para crear macros cortas de una sola línea. Las instrucciones de macro del ensamblador, como las macros en PL/I y algunos otros lenguajes, pueden ser "programas" largos por sí mismas, ejecutadas mediante interpretación por parte del ensamblador durante el ensamblaje.
Dado que las macros pueden tener nombres "cortos" pero expandirse a varias o incluso muchas líneas de código, se pueden usar para hacer que los programas en lenguaje ensamblador parezcan mucho más cortos y requieran menos líneas de código fuente, como ocurre con los lenguajes de nivel superior. También se pueden usar para agregar niveles más altos de estructura a los programas ensambladores, opcionalmente introducir código de depuración incorporado a través de parámetros y otras características similares.
Los ensambladores de macros a menudo permiten que las macros tomen parámetros . Algunos ensambladores incluyen lenguajes de macros bastante sofisticados, que incorporan elementos de lenguaje de alto nivel como parámetros opcionales, variables simbólicas, condicionales, manipulación de cadenas y operaciones aritméticas, todos utilizables durante la ejecución de una macro determinada y que permiten a las macros guardar contexto o intercambiar información. . Por lo tanto, una macro podría generar numerosas instrucciones en lenguaje ensamblador o definiciones de datos, basadas en los argumentos de la macro. Esto podría usarse para generar estructuras de datos de estilo registro o bucles " desenrollados ", por ejemplo, o podría generar algoritmos completos basados en parámetros complejos. Por ejemplo, una macro de "clasificación" podría aceptar la especificación de una clave de clasificación compleja y generar código diseñado para esa clave específica, sin necesitar las pruebas en tiempo de ejecución que se requerirían para un procedimiento general que interpreta la especificación. Se puede considerar que una organización que utiliza lenguaje ensamblador que se ha ampliado en gran medida utilizando dicho conjunto de macros está trabajando en un lenguaje de nivel superior, ya que dichos programadores no están trabajando con los elementos conceptuales de nivel más bajo de una computadora. Subrayando este punto, se utilizaron macros para implementar una de las primeras máquinas virtuales en SNOBOL4 (1967), que fue escrita en SNOBOL Implementation Language (SIL), un lenguaje ensamblador para una máquina virtual. La máquina de destino traduciría esto a su código nativo utilizando un ensamblador de macros . [30] Esto permitió un alto grado de portabilidad para la época.
Las macros se utilizaron para personalizar sistemas de software a gran escala para clientes específicos en la era del mainframe y también fueron utilizadas por el personal del cliente para satisfacer las necesidades de sus empleadores al crear versiones específicas de los sistemas operativos del fabricante. Esto lo hicieron, por ejemplo, programadores de sistemas que trabajaban con el Sistema de Monitor Conversacional/Máquina Virtual ( VM/CMS ) de IBM y con los complementos de "procesamiento de transacciones en tiempo real" de IBM, el Sistema de Control de Información del Cliente CICS y ACP / TPF . el sistema financiero/aéreo que comenzó en la década de 1970 y que aún hoy ejecuta muchos grandes sistemas de reservas por computadora (CRS) y sistemas de tarjetas de crédito.
También es posible utilizar únicamente las capacidades de procesamiento de macros de un ensamblador para generar código escrito en lenguajes completamente diferentes, por ejemplo, para generar una versión de un programa en COBOL usando un programa ensamblador de macros puro que contiene líneas de código COBOL dentro de operadores de tiempo de ensamblaje. ordenar al ensamblador que genere código arbitrario. IBM OS/360 utiliza macros para realizar la generación del sistema . El usuario especifica opciones codificando una serie de macros de ensamblador. El ensamblaje de estas macros genera un flujo de trabajo para construir el sistema, incluido el lenguaje de control de trabajos y las declaraciones de control de servicios públicos .
Esto se debe a que, como se comprendió en la década de 1960, el concepto de "procesamiento macro" es independiente del concepto de "ensamblaje", siendo el primero en términos modernos más procesamiento de textos, procesamiento de texto, que generación de código objeto. El concepto de procesamiento de macros apareció y aparece en el lenguaje de programación C, que admite "instrucciones de preprocesador" para establecer variables y realizar pruebas condicionales sobre sus valores. A diferencia de ciertos macroprocesadores anteriores dentro de ensambladores, el preprocesador de C no es Turing completo porque carece de la capacidad de realizar un bucle o "ir a", lo que permite que los programas realicen un bucle.
A pesar del poder del procesamiento de macros, cayó en desuso en muchos lenguajes de alto nivel (con las principales excepciones C , C++ y PL/I), aunque siguió siendo un elemento perenne para los ensambladores.
La sustitución de parámetros de macro se realiza estrictamente por nombre: en el momento del procesamiento de la macro, el valor de un parámetro se sustituye textualmente por su nombre. La clase de error más famosa resultante fue el uso de un parámetro que en sí mismo era una expresión y no un simple nombre cuando el escritor de macros esperaba un nombre. En la macro:
foo: macro acargar a*b
la intención era que la persona que llama proporcionara el nombre de una variable, y la variable "global" o constante b se usaría para multiplicar "a". Si se llama a foo con el parámetro a-c
, se produce la macro expansión de load a-c*b
. Para evitar cualquier posible ambigüedad, los usuarios de macroprocesadores pueden poner entre paréntesis los parámetros formales dentro de las definiciones de macro, o las personas que llaman pueden poner entre paréntesis los parámetros de entrada. [31]
Se han escrito paquetes de macros que proporcionan elementos de programación estructurados para codificar el flujo de ejecución. El primer ejemplo de este enfoque fue el conjunto de macros Concept-14, [32] propuesto originalmente por Harlan Mills (marzo de 1970) e implementado por Marvin Kessler en la División de Sistemas Federales de IBM, que proporcionaba IF/ELSE/ENDIF y flujos de control similares. bloques para programas ensambladores de OS/360. Esta era una forma de reducir o eliminar el uso de operaciones GOTO en código ensamblador, uno de los principales factores que causan el código espagueti en lenguaje ensamblador. Este enfoque fue ampliamente aceptado a principios de la década de 1980 (los últimos días del uso del lenguaje ensamblador a gran escala). El High Level Assembler Toolkit de IBM [33] incluye dicho paquete de macros.
Otro diseño fue A-Natural, [34] un ensamblador "orientado a flujos" para procesadores 8080/ Z80 de Whitesmiths Ltd. (desarrolladores del sistema operativo Idris , similar a Unix , y lo que se informó fue el primer compilador comercial de C ). El lenguaje se clasificó como ensamblador porque trabajaba con elementos de máquina sin procesar, como códigos de operación , registros y referencias de memoria; pero incorporó una sintaxis de expresión para indicar el orden de ejecución. Los paréntesis y otros símbolos especiales, junto con construcciones de programación estructuradas orientadas a bloques, controlaban la secuencia de las instrucciones generadas. A-natural se creó como lenguaje objeto de un compilador de C, en lugar de para codificación manual, pero su sintaxis lógica ganó algunos seguidores.
Ha habido poca demanda aparente de ensambladores más sofisticados desde el declive del desarrollo del lenguaje ensamblador a gran escala. [35] A pesar de eso, todavía se están desarrollando y aplicando en casos donde las limitaciones de recursos o las peculiaridades en la arquitectura del sistema de destino impiden el uso efectivo de lenguajes de nivel superior. [36]
Los ensambladores con un potente motor de macros permiten la programación estructurada a través de macros, como la macro de conmutación proporcionada con el paquete Masm32 (este código es un programa completo):
incluir \ masm32 \ incluir \ masm32rt.inc ; utilizar la biblioteca Masm32 .code demomain: REPETIR 20 cambiar rv ( nrandom , 9 ) ; generar un número entre 0 y 8 mov ecx , 7 case 0 print "case 0" case ecx ; a diferencia de la mayoría de los otros lenguajes de programación, imprima "caso 7" ; el switch Masm32 permite "casos variables" caso 1 .. 3 .if eax == 1 print "caso 1" .elseif eax == 2 print "caso 2" .else print "casos 1 a 3: otros" .endif caso 4 , 6 , 8 print "casos 4, 6 u 8" predeterminado mov ebx , 19 ; imprimir 20 estrellas . Repetir imprimir "*" dec ebx . ¿ Hasta firmar? ; bucle hasta que se establezca el indicador de signo termina w print chr$ ( 13 , 10 ) ENDM salir fin demomain
Los lenguajes ensambladores no estaban disponibles en el momento en que se introdujo la computadora con programas almacenados . Los programas se escribían en código de máquina absoluto y se cargaban en la computadora desde una cinta de papel perforada o se conmutaban directamente a la memoria desde los interruptores de la consola. [ cita necesaria ] A Kathleen Booth "se le atribuye la invención del lenguaje ensamblador" [37] [38] basándose en el trabajo teórico que comenzó en 1947, mientras trabajaba en el ARC2 en Birkbeck, Universidad de Londres, tras la consulta de Andrew Booth (más tarde su marido) con el matemático John von Neumann y el físico Herman Goldstine en el Instituto de Estudios Avanzados . [38] [39]
A finales de 1948, la Calculadora automática de almacenamiento con retardo electrónico (EDSAC) tenía un ensamblador (llamado "órdenes iniciales") integrado en su programa de arranque . Utilizó mnemónicos de una letra desarrollados por David Wheeler , a quien la IEEE Computer Society acredita como el creador del primer "ensamblador". [20] [40] [41] Los informes sobre la EDSAC introdujeron el término "ensamblaje" para el proceso de combinar campos en una palabra de instrucción. [42] SOAP ( Programa de ensamblaje óptimo simbólico ) era un lenguaje ensamblador para la computadora IBM 650 escrito por Stan Poley en 1955. [43]
Los lenguajes ensambladores eliminan gran parte de la programación de primera generación, propensa a errores, tediosa y que consume mucho tiempo, necesaria con las primeras computadoras, liberando a los programadores del tedio como recordar códigos numéricos y calcular direcciones. Alguna vez fueron ampliamente utilizados para todo tipo de programación. Sin embargo, a finales de la década de 1950, [44] su uso había sido suplantado en gran medida por lenguajes de nivel superior, en la búsqueda de una mayor productividad de la programación . [ cita necesaria ] Hoy en día, el lenguaje ensamblador todavía se usa para la manipulación directa del hardware, el acceso a instrucciones especializadas del procesador o para abordar problemas críticos de rendimiento. [45] Los usos típicos son controladores de dispositivos , sistemas integrados de bajo nivel y sistemas en tiempo real (ver § Uso actual).
Numerosos programas se han escrito íntegramente en lenguaje ensamblador. El Burroughs MCP (1961) fue el primer ordenador para el que no se desarrolló un sistema operativo íntegramente en lenguaje ensamblador; fue escrito en lenguaje orientado a problemas de sistemas ejecutivos (ESPOL), un dialecto de Algol. Muchas aplicaciones comerciales también se escribieron en lenguaje ensamblador, incluida una gran cantidad de software de mainframe IBM escrito por grandes corporaciones. COBOL , FORTRAN y algunos PL/I eventualmente desplazaron gran parte de este trabajo, aunque varias organizaciones grandes conservaron infraestructuras de aplicaciones en lenguaje ensamblador hasta bien entrada la década de 1990.
El lenguaje ensamblador ha sido durante mucho tiempo el principal lenguaje de desarrollo para computadoras domésticas de 8 bits, como Apple II , Atari de 8 bits , MSX , ZX Spectrum y Commodore 64 . Los dialectos BÁSICOS interpretados en estos sistemas ofrecen una velocidad de ejecución insuficiente y facilidades insuficientes para aprovechar al máximo el hardware disponible. Estos sistemas tienen graves limitaciones de recursos, memoria idiosincrásica y arquitecturas de visualización, y proporcionan servicios de sistema limitados. También existen pocos compiladores de lenguajes de alto nivel adecuados para uso en microcomputadoras. De manera similar, el lenguaje ensamblador es la opción predeterminada para consolas de 8 bits como Atari 2600 y Nintendo Entertainment System .
El software clave para PC compatibles con IBM se escribió en lenguaje ensamblador, como MS-DOS , Turbo Pascal y la hoja de cálculo Lotus 1-2-3 . A medida que la velocidad de la computadora creció exponencialmente, el lenguaje ensamblador se convirtió en una herramienta para acelerar partes de programas, como la representación de Doom , en lugar de un lenguaje de desarrollo dominante. En la década de 1990, el lenguaje ensamblador se usaba para obtener rendimiento de sistemas como Sega Saturn [46] y como lenguaje principal para hardware arcade basado en la CPU/GPU integrada TMS34010 , como Mortal Kombat y NBA Jam .
Ha habido debate sobre la utilidad y el rendimiento del lenguaje ensamblador en relación con los lenguajes de alto nivel. [47]
Aunque el lenguaje ensamblador tiene usos específicos donde es importante (ver más abajo), existen otras herramientas para la optimización. [48]
En julio de 2017 [actualizar], el índice TIOBE de popularidad de lenguajes de programación clasifica al lenguaje ensamblador en el puesto 11, por delante de Visual Basic , por ejemplo. [49] El ensamblador se puede utilizar para optimizar la velocidad u optimizar el tamaño. En el caso de la optimización de la velocidad, se afirma que los compiladores de optimización modernos [50] convierten lenguajes de alto nivel en código que puede ejecutarse tan rápido como un ensamblaje escrito a mano, a pesar de los contraejemplos que se pueden encontrar. [51] [52] [53] La complejidad de los procesadores y subsistemas de memoria modernos hace que la optimización efectiva sea cada vez más difícil para los compiladores, así como para los programadores de ensamblaje. [54] [55] Además, el aumento del rendimiento del procesador ha significado que la mayoría de las CPU permanecen inactivas la mayor parte del tiempo, [56] con retrasos causados por cuellos de botella predecibles, como errores de caché, operaciones de E/S y paginación . Esto ha hecho que la velocidad de ejecución del código sin formato no sea un problema para muchos programadores.
Hay algunas situaciones en las que los desarrolladores pueden optar por utilizar lenguaje ensamblador:
El lenguaje ensamblador todavía se enseña en la mayoría de los programas de informática e ingeniería electrónica . Aunque hoy en día pocos programadores trabajan regularmente con el lenguaje ensamblador como herramienta, los conceptos subyacentes siguen siendo importantes. Temas fundamentales como la aritmética binaria , la asignación de memoria , el procesamiento de pilas , la codificación de juegos de caracteres , el procesamiento de interrupciones y el diseño de compiladores serían difíciles de estudiar en detalle sin comprender cómo opera una computadora a nivel de hardware. Dado que el comportamiento de una computadora está definido fundamentalmente por su conjunto de instrucciones, la forma lógica de aprender tales conceptos es estudiar un lenguaje ensamblador. La mayoría de las computadoras modernas tienen conjuntos de instrucciones similares. Por lo tanto, estudiar un solo lenguaje ensamblador es suficiente para aprender: I) los conceptos básicos; II) reconocer situaciones donde el uso del lenguaje ensamblador podría ser apropiado; y III) ver cómo se puede crear código ejecutable eficiente a partir de lenguajes de alto nivel. [23]
El lenguaje ensamblador también puede denominarse código de máquina simbólico.
Programar en lenguaje ensamblador tiene los mismos beneficios que programar en lenguaje de máquina, excepto que es más fácil.
El lenguaje ensamblador (o ensamblador) es un lenguaje informático compilado de bajo nivel. Depende del procesador ya que básicamente traduce los mnemotécnicos del ensamblador directamente en los comandos que entiende una CPU en particular, uno a uno. Estos mnemotécnicos del ensamblador son el conjunto de instrucciones para ese procesador.
El lenguaje ensamblador suele ser específico de una arquitectura informática particular, por lo que existen varios tipos de lenguajes ensambladores. ARM es un lenguaje ensamblador cada vez más popular.
Utilizado como metaensamblador, permite al usuario diseñar sus propios lenguajes de programación y generar procesadores para dichos lenguajes con un mínimo de esfuerzo.
La siguiente restricción o limitación menor está vigente con respecto al uso de 1401 Autocoder al codificar instrucciones macro...
Las ideas no originales, contenidas en el siguiente texto, se han derivado de varias fuentes... Sin embargo, se considera que se debe agradecer al Prof. John von Neumann y al Dr. Herman Goldstein por sus muchas y fructíferas discusiones...
{{cite web}}
: Mantenimiento CS1: URL no apta ( enlace )Siempre existe un debate sobre la aplicabilidad del lenguaje ensamblador en nuestro mundo de programación moderno.
... los cambios de diseño tienden a afectar el rendimiento más que ... uno no debe pasar directamente al lenguaje ensamblador hasta ...