¿Qué es un compilador?
Aprende cómo funciona un compilador y por qué a veces el código no se compila
En esta guía aprenderás qué es un compilador y cómo funciona. Analizaremos las etapas de compilación y qué determina la elección de un compilador adecuado. Este material te ayudará a comprender mejor cómo el ordenador ejecuta el código de un programa y por qué a veces el código no se compila.
¿Para qué sirve un compilador?
El procesador es la parte más importante de un ordenador. Procesa información, ejecuta comandos del usuario y supervisa el funcionamiento de todos los dispositivos conectados. Sin embargo, el procesador solo puede entender el código de máquina, una serie de 0 y 1 escritos en un orden específico.
¿Por qué 0 y 1? El procesador recibe señales eléctricas. Una señal fuerte se representa con el número 1, mientras que una señal débil se representa con el número 0. Una serie de estos números representa un comando. El procesador lo reconoce y lo ejecuta.
Los programas para los primeros ordenadores consistían en enormes series de 0 y 1. Para escribir un programa de este tipo, los ingenieros utilizaban tarjetas perforadas de cartón flexibles. Los números en las tarjetas se escribían en varias filas, uno tras otro. Para escribir un 1, el programador hacía un agujero en la tarjeta. Las áreas sin agujeros se representaban con un 0.

El ordenador leía la tarjeta perforada con un dispositivo especial y ejecutaba el comando escrito en ella. Para un solo programa, se utilizaban cientos de tarjetas perforadas.
Escribir estos programas era lento y complicado, por lo que los ingenieros comenzaron a crear lenguajes de programación, utilizando palabras y signos para representar comandos. Para que el procesador entendiera qué comandos estaban escritos en el programa, los programadores crearon un compilador, un programa que convierte el código de programación en código de máquina.
¿Cómo funciona un compilador?
La conversión del código de programación en código de máquina se llama compilación. La compilación solo transforma el código, no lo ejecuta. En este momento, se traduce "estáticamente" (es decir, sin ejecución) a código de máquina. Este es un proceso complejo en el que primero se descompone y analiza el texto del programa, y luego se genera un código que el procesador puede entender.

#include <cstdio>
int main()
{
double a=2.5, b=5, P;
P = 2 * (a + b);
printf("Ancho del rectángulo - %4.1f", a);// => Ancho del rectángulo - 2.5
printf("\nLargo del rectángulo - %4.1f", b);// => Largo del rectángulo - 5.0
printf("\nEl perímetro del rectángulo es %4.1f", P);// => El perímetro del rectángulo es 15.0
return 0;
}
Al compilar el programa, el compilador necesita determinar qué comandos están escritos en él. Primero, el compilador divide el programa en palabras y signos, llamados tokens, y los guarda en una lista. Este proceso se llama análisis léxico. Su principal objetivo es obtener los tokens.
# include <cstdio> int main ( ) { double a = 2.5 , b = 5 , P ; P = 2 * ( a + b ) ; printf ( " Ancho del rectángulo - % 4.1 f " , a ) ; printf ( " \ n Largo del rectángulo - % 4.1 f " , b ) ; printf ( " \ n El perímetro del rectángulo es % 4.1 f " , P ) ; return 0 ; }
Luego, el compilador lee la lista y busca tokens que sean operadores. Estos pueden ser operadores de asignación (=), operadores aritméticos (+, -, *, /), operadores de impresión (printf()) y otros operadores del lenguaje de programación. Estos operadores trabajan con números, texto y variables.
El compilador debe entender qué tokens en la lista están relacionados con un operador. Para hacerlo correctamente, se construye una estructura especial para cada operador: un árbol de análisis o árbol de sintaxis.
Así, la operación P = 2*(a + b) se convertirá en un árbol de sintaxis:

Ahora, cada árbol debe descomponerse en comandos y cada comando debe convertirse en código de máquina. El compilador comienza a leer el árbol de abajo hacia arriba y crea una lista de comandos:
- Tomar la variable a, tomar la variable b, sumarlas
- Tomar el resultado de la suma, tomar el número 2 y multiplicarlos
- Asignar (escribir) el resultado de la multiplicación en la variable P
El compilador verifica los comandos nuevamente, encuentra errores y trata de mejorar el código. Si este paso se completa con éxito, el compilador convierte cada comando en una serie de 0 y 1. Estas series se escriben en un archivo que el procesador puede leer y ejecutar. El bloque siguiente es solo un ejemplo ilustrativo del aspecto del código de máquina: no corresponde exactamente al programa de arriba, porque el resultado real depende del compilador, del procesador y del sistema operativo.
10111011 00010001 00000001 10111001 00001101 00000000 10110100 00001110 10001010 00000111 01000011 11001101 00010000 11100010 11111001 11001101 00100000 01001000 01100101 01101100 01101100 01101111 00101100 00100000 01010111 01101111 01110010 01101100 01100100 00100001
¿En qué está escrito un compilador?
En la década de 1950, un grupo de desarrolladores de IBM liderado por John Backus desarrolló uno de los primeros lenguajes de programación de alto nivel, Fortran, que permitía escribir programas en un lenguaje comprensible para los humanos. Además del lenguaje, los ingenieros también trabajaron en su compilador.
Los primeros compiladores solían escribirse en ensamblador o directamente en código de máquina. Escribir así es lento e incómodo, y además el código cambia de un procesador a otro, así que habría que escribir varias versiones del mismo compilador para diferentes ordenadores. Por eso surgió la idea de la autocompilación (en inglés, bootstrapping): se escribe una primera versión sencilla del compilador con las herramientas disponibles y, después, el compilador se reescribe en el propio lenguaje que compila. Así puede compilar una versión mejorada de sí mismo. Se suele considerar que el primer compilador que se compilaba a sí mismo fue el de Lisp, creado por Hart y Levin en el MIT en 1962.
Los programadores siguen usando este enfoque en la actualidad. Es más rápido y sencillo escribir un compilador en un lenguaje existente y conveniente, y el resultado es más versátil para distintos ordenadores. Hoy muchos compiladores están escritos en el lenguaje que compilan, por ejemplo los de Go y Rust.

¿Qué tipos de compiladores existen?
Ningún lenguaje de programación compilado puede prescindir de un compilador. Algunos compiladores trabajan con varios lenguajes de programación. Sin embargo, el programador también debe tener en cuenta los parámetros del ordenador en el que se ejecutará el programa.
El motivo es que los procesadores modernos difieren en su estructura, por lo que el código de máquina para un procesador puede ser comprensible, mientras que para otro no. Esto también se aplica a los sistemas operativos: un programa puede funcionar en Windows, pero no se ejecutará en Linux o macOS. Por lo tanto, debes utilizar el compilador adecuado que funcione con el procesador y el sistema operativo necesarios.
Un compilador cruzado (en inglés, cross-compiler) se ejecuta en una plataforma y genera código para otra distinta, por ejemplo, compilar en Windows un programa para Linux o para un procesador ARM. Para que un programa funcione en varios sistemas operativos, lo habitual es compilar un archivo ejecutable distinto para cada plataforma o usar un lenguaje que se ejecuta en una máquina virtual. Por ejemplo, GNU Compiler Collection (GCC) admite C, C++, Objective-C, Fortran, Ada, D y Go, entre otros lenguajes, y diferentes arquitecturas de procesadores.
Los programadores principiantes ni siquiera saben que tienen un compilador en su ordenador. Escriben programas en un entorno de desarrollo integrado que incluye un compilador, a veces más de uno. En este caso, el entorno elige el compilador, no el programador. Por ejemplo, MS Visual Studio admite compiladores para los sistemas operativos Windows, Linux y Android. Al elegir el tipo de proyecto, Visual Studio determina el procesador y el sistema operativo del ordenador y luego selecciona el compilador adecuado.
¿Qué errores puede detectar un compilador?
Cuando el compilador analiza el texto del programa, verifica si el enunciado del operador cumple con los estándares del lenguaje. Si encuentra alguna discrepancia, el compilador muestra un mensaje de error. Hasta que el programador corrija los errores, el compilador no pasará a la siguiente etapa, que es generar el código de máquina para el procesador. En general, el compilador muestra al usuario:
- errores de declaración de variables o falta de valores iniciales
- errores de incompatibilidad de tipos
- errores de sintaxis en la escritura de operadores y funciones
A veces, el compilador puede detectar código que produce un resultado incorrecto durante la ejecución. Sin embargo, aún es posible convertir dicho programa en código de máquina. En este caso, el compilador muestra una advertencia al usuario. Esta respuesta del compilador se asemeja más a una recomendación, pero vale la pena prestar atención a ella. El programador decide si dejar el código con la advertencia o modificar el programa. Al analizar el texto del programa, el compilador no solo busca errores, sino que también simplifica el código. Este proceso se llama optimización. Durante la optimización, el compilador modifica el código del programa, pero las funciones que realiza el programa siguen siendo las mismas.
Conclusiones y recomendaciones
Un compilador es un traductor entre el programador y el procesador. Convierte el texto del programa en código de máquina, detecta errores en el programa y optimiza su funcionamiento. Al elegir dónde compilar un programa, es importante recordar que el código de máquina para procesadores y sistemas operativos será diferente, por lo que debes seleccionar el compilador adecuado. Los compiladores modernos, como GCC o LLVM, optimizan el código por su cuenta, así que la mayor parte de la velocidad no depende de "ayudar" al compilador con trucos. Para sacar el mejor partido de la compilación, sigue estas recomendaciones sencillas:
- escribe código simple y legible, pensando en quien lo leerá después;
- ten en cuenta la correspondencia de los tipos de datos;
- escribe el código con cuidado, evitando errores de sintaxis;
- lee las advertencias del compilador y no las ignores;
- si necesitas más velocidad, activa las opciones de optimización del compilador (por ejemplo,
-O2en GCC) en lugar de complicar el código.
Compilación hoy
La compilación clásica a código de máquina, como la que describimos arriba, es solo una de las formas de ejecutar un programa. Muchos lenguajes actuales combinan varias ideas.
Algunos lenguajes, como Java, compilan primero a un bytecode, un código intermedio que ejecuta una máquina virtual. Los motores de JavaScript, como V8, y otras implementaciones usan la compilación JIT (just in time), que traduce partes del programa a código de máquina mientras se ejecuta. Por eso la frontera entre "compilador" e "intérprete" hoy es menos nítida. Sobre el segundo concepto puedes leer la guía ¿Qué es un intérprete?.
También existe la transpilación, que traduce código de un lenguaje a otro del mismo nivel, como cuando TypeScript se convierte en JavaScript. Lo explicamos en la guía ¿Qué es la transpilación?. Y con WebAssembly, programas escritos en lenguajes como C, C++ o Rust pueden compilarse para ejecutarse en el navegador.
Otro cambio es la arquitectura del procesador: además de x86, hoy son comunes los procesadores ARM, como los de los Mac con chips Apple Silicon y los de muchos teléfonos. El mismo código fuente debe compilarse por separado para cada arquitectura, y los compiladores modernos permiten elegir el destino de la compilación.
Preguntas frecuentes
¿Cuál es la diferencia entre un compilador y un intérprete?
Un compilador es un programa que realiza la conversión del texto del programa a otro formato, generalmente código de máquina, sin ejecutarlo, de forma estática. Luego, este programa se puede ejecutar. Un [intérprete](https://es.wikipedia.org/wiki/Int%C3%A9rprete_(inform%C3%A1tica%29) ejecuta el código de inmediato y lo ejecuta durante la lectura. No hay una etapa intermedia como en la compilación. En la práctica la frontera es difusa, porque muchos entornos combinan ambos enfoques (por ejemplo, con compilación JIT).
Recursos adicionales
- Compilador en Wikipedia
- ¿Qué es un intérprete?
- ¿Qué es la transpilación?