PhD: construye lo imposible
El nivel Dios: escribe tu propia libc mínima y tu propio allocator, asómate a cómo funciona un compilador por dentro, y descubre que el camino de sistemas no termina nunca.
Has recorrido todo el mapa: del primer printf al Undefined Behavior. Este último nivel no enseña un tema nuevo — te reta a demostrar que lo entiendes construyendo lo que antes usabas sin pensar. Si puedes escribir tu propia libc y tu propio allocator, y entender cómo un compilador convierte tu C en máquina, eres, de verdad, nivel dios.
- Escribir una libc mínima desde cero.
- Implementar un allocator propio.
- Entender cómo funciona un compilador por dentro.
- Ver por qué el camino no termina.
Reto 1: tu propia libc mínima
En el nivel 23 corriste código freestanding. Ahora conviértelo en una mini-libc: implementa, desde syscalls crudas, las funciones que dabas por sentadas.
// escribir a la salida estándar, sin la libc: solo la syscall write
long sys_write(int fd, const void *buf, unsigned long n) {
long r;
__asm__ volatile ("syscall" : "=a"(r)
: "a"(1), "D"(fd), "S"(buf), "d"(n) : "rcx", "r11", "memory");
return r;
}
size_t mi_strlen(const char *s) { // tu strlen
const char *p = s;
while (*p) p++;
return (size_t)(p - s);
}
int mi_puts(const char *s) { // tu puts, sobre tu strlen y sys_write
sys_write(1, s, mi_strlen(s));
sys_write(1, "\n", 1);
return 0;
}
Hay una diferencia abismal entre usar strlen y escribir strlen. Usarla requiere saber qué hace; escribirla requiere entender punteros, el terminador nulo, size_t, la aritmética de direcciones — todo junto y correcto. Cuando reimplementas una pieza de la libc desde syscalls, demuestras que no hay magia debajo que no comprendas: sabes exactamente qué hace la máquina en cada paso, porque lo has escrito. Esta es la razón por la que los grandes programadores de sistemas reconstruyen las cosas al menos una vez. No para usar su versión en producción, sino porque después de hacerlo, la caja negra se vuelve transparente para siempre.
Reto 2: tu propio allocator
Implementa malloc y free desde cero, pidiendo memoria al sistema con mmap o sbrk y gestionándola tú. Empieza simple —una lista libre de bloques— y evoluciona:
// esqueleto: pide memoria al kernel y gestiona una lista de bloques libres.
// cada bloque lleva una cabecera con su tamaño y si está libre.
// malloc: busca un hueco suficiente (o pide más al kernel);
// free: marca el bloque libre y fusiona con vecinos libres (coalescing).
void *mi_malloc(size_t n);
void mi_free(void *p);
Al hacerlo entenderás en carne propia la fragmentación, la alineación (nivel 14), las cabeceras de bloque, y por qué las arenas y pools existen. Habrás cerrado el círculo del nivel 13.
Cómo funciona un compilador por dentro
Has usado gcc y clang cientos de veces. ¿Qué hacen realmente? Un compilador convierte tu C en máquina por fases:
flowchart LR S[código C] --> L[lexer: tokens] L --> P[parser: AST] P --> I[IR intermedio] I --> O[optimización: pasadas] O --> C[codegen: ensamblador] style S fill:#89b4fa,color:#11111b style I fill:#cba6f7,color:#11111b style O fill:#fab387,color:#11111b style C fill:#a6e3a1,color:#11111b
- El lexer parte el texto en tokens (
int,x,=,5). - El parser los organiza en un árbol de sintaxis (AST) según la gramática.
- Se traduce a una representación intermedia (IR) — en LLVM, el famoso “LLVM IR”.
- Las pasadas de optimización transforman el IR (es aquí donde se explota el UB del nivel 29, se hace inlining, LTO, vectorización).
- El codegen emite ensamblador para tu arquitectura, respetando la ABI (nivel 21).
Entender esta tubería cierra tu modelo mental de todo el track. Ahora ves por qué el UB permite optimizaciones (ocurren en las pasadas sobre el IR), por qué la ABI importa (el codegen la respeta), por qué LTO ve el programa entero (optimiza el IR combinado), y por qué los sanitizers instrumentan (insertan código en el IR). El compilador es, a la vez, el programa de sistemas más complejo que usarás y la lente que unifica todo lo que has aprendido. LLVM y GCC son proyectos abiertos: puedes leer su código, ver el IR de tu programa (clang -S -emit-llvm), y hasta contribuir. Ahí está la frontera.
El camino no termina
Llegar aquí no significa “saberlo todo” — significa tener los cimientos para aprender cualquier cosa del mundo de los sistemas sin que nada te resulte magia. Desde aquí, las fronteras: contribuir a LLVM, GCC o musl; escribir un kernel de juguete (o el de verdad, en el siguiente track); explorar la verificación formal (demostrar matemáticamente que el código es correcto); el rendimiento extremo del HPC; o los lenguajes que intentan dar la seguridad de la que C carece — como Rust, que te espera en su propio track. Has aprendido a leer el estándar, a pensar en memoria, a no temer al metal. Eso no caduca y se transfiere a todo. El programador de sistemas nunca termina de aprender; simplemente, deja de tenerle miedo a lo desconocido. Bienvenido al nivel dios.
- Escribe una mini-libc:
sys_write, tustrlen, tuputs, y un “hola mundo” que no enlace la libc real. - Implementa un
malloc/freepropio con una lista libre y coalescing. Pruébalo bajo AddressSanitizer. - Mira el LLVM IR de un programa tuyo con
clang -S -emit-llvm -O2y reconoce lo que hace el optimizador. - Elige tu siguiente frontera: ¿el kernel de Linux? ¿Rust? ¿un compilador? El mapa del nivel 0 ahora es tuyo entero.