Escribir un programa en C válido sin función main(): tutorial paso a paso

Fuentes: Writing a (valid) C program without main() | iximiuz Labs

Este tutorial técnico de iximiuz Labs muestra cómo construir un ejecutable en C que prescinde de la función main() y, opcionalmente, de toda la biblioteca estándar, recurriendo directamente a las llamadas al sistema del kernel de Linux. El recorrido parte de un programa «Hola, mundo» convencional compilado en Ubuntu 24.04 con gcc y avanza por las cuatro etapas del proceso de compilación: preprocesador (cpp), compilador (cc1), ensamblador (as) y enlazador (ld o collect2). En cada paso se utilizan los flags -E, -S y -c de gcc para detener el proceso y observar el resultado: macros expandidas, código ensamblador x86_64 con prólogo de función y llamadas a printf, y archivos objeto con símbolos como main y referencias sin resolver a libc.

A continuación se compara el ensamblador generado con niveles de optimización -O0, -O2 y -Os, comprobando cómo el compilador puede desplegar, vectorizar o sustituir un bucle por su fórmula cerrada. El tutorial explica además el papel de los ficheros crt1.o, crti.o y crtn.o: contienen el símbolo _start, verdadero punto de entrada al que salta el kernel antes de invocar main(). Al usar la opción -nostartfiles, el enlazador omite ese código de arranque, lo que obliga a definir un _start propio en ensamblador.

El primer intento, que aún llama a printf, falla porque glibc no se ha inicializado y stdout no está listo. La versión definitiva elimina cualquier dependencia de libc: una función my_message devuelve un puntero a una cadena, y un _start escrito a mano usa las syscalls write (número 1) y exit (número 60) de Linux en x86_64 para imprimir el mensaje y terminar con código 0. El binario resultante es un ELF válido que el kernel puede cargar y ejecutar sin main(), sin libc y sin código de arranque del runtime de C, ilustrando de forma práctica la frontera entre espacio de usuario y núcleo.