Es bien sabido que la diferencia de tamaño entre un binario compilado y uno escrito a mano en ensamblador optimizado puede ser enorme. El compilador arrastra todo tipo de boilerplate use o no las funciones, y eso explica el espacio extra. Weineng se metió en la madriguera de achicar al máximo un ejecutable de C compilado con gcc, y el write-up resultante es una lectura fascinante.
Sorpresivamente, el programa de C más pequeño no es Hello World, sino uno que no hace absolutamente nada y solo retorna 0. Aun así, el binario resultante pesa unos sorprendentes 15.816 bytes, algo que parece tener margen para mejorar.
A partir de ahí, viene una serie de flags de compilador y manipulaciones de código que remueven info de debug y se deshacen del código innecesario ejecutado antes de void main().
Con 13.632 bytes el binario sigue siendo pesado, así que el siguiente paso es revisar qué librerías arrastra. Más flags lo dejan en 8.704 bytes. Quitar una sección de comentarios y el manejo de errores con flags adicionales lo lleva a 4.320 bytes. Después viene el código que dicta cómo se asigna memoria, que baja la cuenta a 400 bytes. Una reducción enorme.
¿Por qué nos interesa desde el lado del hardware?
Leyendo esto como gente de hardware, probablemente no tenemos el conocimiento experto de flags de compilador que se requiere para llegar a este nivel. Pero todos alguna vez tuvimos que reducir el tamaño de un binario, así que muchas de las técnicas usadas resultan interesantes para quienes vienen del mundo embebido.
Conviene recordar que incluso la gente de hardware necesita recortar grasa de vez en cuando.
Las etapas del ahorro de bytes
| Etapa | Tamaño | Cómo se logra |
|---|---|---|
| Programa que solo retorna 0 | 15.816 bytes | gcc default |
| Sin debug info ni código pre-main | 13.632 bytes | flags de stripping |
| Sin librerías superfluas | 8.704 bytes | gc-sections y similares |
| Sin comentarios ni handling de errores | 4.320 bytes | desactivar unwind tables y handlers |
| Sin código de allocator | 400 bytes | reemplazo del runtime de memoria |
¿Cuán pequeño se puede llegar realmente?
En los comentarios del post aparecen referencias a un thread de Reddit donde alguien logró bajarlo a 84 bytes, con unos 32 bytes dedicados al header ELF. La receta involucra inline assembly, lo que algunos lectores consideran trampa para un ejercicio de C puro.
También hay menciones a formatos sin header, como .com de MS-DOS o CP/M, donde el binario puede ser literalmente código de máquina puro sin overhead de loader. Un guiño obligado viene del mainframe: el utilitario IEFBR14 del IBM 360 pesaba exactamente 4 bytes (la secuencia 1B FF 07 FE), una instrucción que seteaba código de condición 0 y retornaba inmediatamente.
El contraste con los binarios actuales
Para dimensionar lo que arrastra un Linux moderno: el utilitario true de GNU coreutils en un sistema actual pesa cerca de 67.428 bytes. Es decir, una función que solo retorna 0 hoy carga casi un orden de magnitud más overhead que la versión "récord" de Weineng tras todas las optimizaciones.
La comparación funciona como recordatorio de por qué el embedded sigue siendo otro deporte: cuando se trabaja con una flash de 256 KB en un STM32, los 32 KB de un ATmega328 o los pocos KB del flash interno de un ATtiny85, cada byte cuenta y los flags de gcc dejan de ser un detalle académico. La cadena de tooling para microcontroladores (avr-gcc, arm-none-eabi-gcc) replica casi las mismas optimizaciones que documenta Weineng, pero por razones de necesidad antes que de curiosidad.
Datos clave para el lector de embebido
- Reducción total: de 15.816 a 400 bytes equivale a un factor de 39,5×. En memoria flash de microcontroladores, ese tipo de ahorro libera espacio para bibliotecas de drivers o buffers de comunicación.
- El cuello no está en main: la mayoría del peso inicial viene del runtime C (crt0, allocator, unwind tables), no del código del programador. Quien necesite control fino debe atacar esa capa.
- Inline assembly como puerta abierta: si el objetivo es llegar a decenas de bytes, hay que aceptar mezclar lenguajes. Para una aplicación productiva en STM32 o ESP32 la línea razonable está más arriba, en la zona de 1-4 KB, donde gcc puro todavía rinde.



