Proceso de compilación — Desde el código fuente hasta el ejecutable
Al finalizar este tema
Podrás explicar la diferencia entre un compilador y un intérprete, comprender las etapas de conversión del código fuente a lenguaje de máquina y saber cómo se ejecutan Python, JavaScript y C, respectivamente.
La computadora no puede leer el código fuente
El código que escribimos es texto para humanos.
print("Hello, World!")Lo que entiende la CPU es el lenguaje de máquina: instrucciones compuestas por 0 y 1.
10110000 01001000 (mov al, 'H')
11001101 00100001 (int 21h)Es necesaria la conversión del código fuente a código máquina. Los programas que realizan esta conversión son el compilador y el intérprete.
Compilador vs. Intérprete
| Compilador | Intérprete | |
|---|---|---|
| Momento de la conversión | Convierte todo de una vez antes de la ejecución | Convierte línea por línea durante la ejecución |
| Resultado | Archivo ejecutable (binario) | Ninguno (ejecución inmediata) |
| Detección de errores | Muestra todos los errores en el momento de la compilación | El error se detecta al ejecutar la línea correspondiente |
| Velocidad de ejecución | Rápida (ya está convertido) | Relativamente lenta |
| Lenguajes representativos | C, C++, Rust, Go | Python, JavaScript, Ruby |
Analogía: el compilador es como la traducción de un libro (se traduce todo primero y luego se publica), mientras que el intérprete es como la interpretación simultánea (se traduce al instante mientras se habla).
Etapas de la compilación — En el caso de C
El proceso por el cual el código C se convierte en un archivo ejecutable:
Código fuente (.c)
↓ Preprocesado (Preprocessing)
Código preprocesado
↓ Compilación (Compilation)
Código ensamblador (.s)
↓ Ensamblado (Assembly)
Archivo objeto (.o)
↓ Enlazado (Linking)
Ejecutable (a.out / .exe)1. Preprocesamiento
#include <stdio.h>
#define MAX 100
int main() {
printf("Max is %d\n", MAX);
}Reemplace #include con el contenido real del archivo de cabecera y sustituya #define por su valor. Esta etapa es una sustitución de texto.
2. Compilación (Compilation)
Analiza el código preprocesado y lo convierte en código ensamblador. Los errores de sintaxis se detectan en esta etapa.
mov edi, OFFSET FLAT:.LC0
mov esi, 100
call printf3. Ensamblado
Convierte el código de ensamblador en lenguaje máquina (instrucciones binarias). El resultado es un archivo objeto (.o).
4. Enlace
Combina varios archivos objeto y bibliotecas en un único archivo ejecutable. Dado que la implementación real de printf se encuentra en la biblioteca estándar de C, el enlazador se encarga de conectarla.
# GCC realiza todos estos pasos de una vezgcc hello.c -o hello./hello¿Cómo se ejecuta Python?
Python es un modelo híbrido de compilación e interpretación.
Código fuente (.py)
↓ Compilador de Python
Bytecode (.pyc)
↓ Máquina virtual de Python (PVM)
Resultado de ejecución# Inspecciona el bytecodeimport dis
def add(a, b): return a + b
dis.dis(add)# LOAD_FAST 0 (a)# LOAD_FAST 1 (b)# BINARY_ADD# RETURN_VALUEEl bytecode no es lenguaje máquina. Un intérprete llamado Python Virtual Machine (PVM) ejecuta el bytecode línea por línea. Es más lento que cuando la CPU lo ejecuta directamente como en C, pero puede ejecutarse independientemente del sistema operativo.
__pycache__/ habrás visto que se crea un archivo .pyc en la carpeta. Este es el caché de bytecode. Si el código fuente no cambia, se utiliza el caché sin necesidad de recompilar.
Cómo se ejecuta JavaScript
Los motores de JavaScript (V8, SpiderMonkey) utilizan compilación JIT (Just-In-Time).
Código fuente (.js)
↓ Análisis sintáctico
AST (árbol de sintaxis abstracta)
↓ Intérprete
Ejecución de bytecode (lenta)
↓ Compilador JIT (detecta código ejecutado con frecuencia)
Conversión a código máquina (rápida)Al principio, se inicia la ejecución rápidamente mediante un intérprete y, cuando detecta que "esta función se ha llamado 1,000 veces", compila únicamente esa parte a código máquina. Dado que la compilación ocurre durante la ejecución, se denomina "Just-In-Time".
Java — El híbrido clásico entre compilación e interpretación
Java es similar a Python, pero más complejo.
Código fuente (.java)
↓ javac (compilación)
Bytecode (.class)
↓ JVM (Java Virtual Machine)
├── Intérprete (al principio)
└── Compilador JIT (código ejecutado con frecuencia)
↓
Ejecución de código máquina// Hello.java
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}javac Hello.java # Genera bytecode (.class)java Hello # La JVM ejecuta el bytecode"Escribir una vez, ejecutar en cualquier lugar": el bytecode de Java se ejecuta en cualquier lugar que tenga una JVM. Un archivo .class compilado en Windows también funciona en una JVM de Linux.
Tipos estáticos vs. tipos dinámicos
El sistema de tipos es un concepto estrechamente relacionado con la compilación.
// C: tipado estático; comprueba tipos durante la compilación
int x = 42;
x = "hello"; // ¡Error de compilación!# Python: tipado dinámico; comprueba tipos durante la ejecuciónx = 42x = "hello" # OK: el tipo cambia durante la ejecución| Tipado estático | Tipado dinámico | |
|---|---|---|
| Momento de verificación de tipos | En compilación | En ejecución |
| Detección de errores | Antes de la ejecución | Durante la ejecución |
| Declaración de tipos | Obligatoria (int x) | No es necesaria (x = 42) |
| Velocidad de ejecución | Rápida | Relativamente lenta |
| Lenguajes representativos | C, Java, TypeScript | Python, JavaScript, Ruby |
Implicaciones prácticas
¿Por qué Python es más lento que C?
→ C: la CPU ejecuta directamente el código máquina
→ Python: la máquina virtual interpreta y ejecuta el bytecode instrucción por instrucción
¿Por qué usar TypeScript?
→ Añade tipos estáticos a JavaScript y detecta errores antes de ejecutar
→ TypeScript → (compilación tsc) → JavaScript → (ejecución V8)
¿Por qué las imágenes Docker difieren según el sistema operativo?
→ Los binarios compilados dependen de la arquitectura de CPU, como x86 o ARM
→ Un binario para macOS no puede ejecutarse en LinuxResumen clave
| Concepto | Resumen |
|---|---|
| Compilador | Convierte todo el código fuente a código máquina de antemano. Ejecución rápida |
| Intérprete | Interpreta y ejecuta línea por línea. Desarrollo rápido |
| Bytecode | Forma intermedia. Ejecutado por una máquina virtual (Python, Java) |
| JIT | Compila a código máquina solo las partes que se usan con frecuencia durante la ejecución (JavaScript) |
| Tipado estático | Verifica los tipos en tiempo de compilación (C, TypeScript) |
| Tipado dinámico | Verifica los tipos en tiempo de ejecución (Python, JavaScript) |
Herramientas de construcción y transpiladores
En el desarrollo moderno, además de la compilación e interpretación "puras", existen diversas herramientas de transformación.
TypeScript → (tsc) → JavaScript → (V8) → ejecución
JSX/React → (Babel) → JavaScript → (V8) → ejecución
Sass/SCSS → (sass) → CSS → renderizado del navegadorEl transpilador realiza una conversión entre lenguajes del mismo nivel. Mientras que un compilador realiza una conversión de alto nivel a bajo nivel, un transpilador realiza una conversión de alto nivel a alto nivel. Los ejemplos más representativos son TypeScript→JavaScript y ES6→ES5.
Decir que "Python es un lenguaje interpretado" es solo parcialmente correcto: se compila a bytecode y luego se interpreta. Decir que "JavaScript es un lenguaje interpretado" también es solo parcialmente correcto: V8 realiza compilación JIT. Los lenguajes modernos combinan la compilación y la interpretación para buscar tanto la facilidad de desarrollo como el rendimiento de ejecución.