wandres.dev
NOVATO · Por qué C hoy

Dónde vive C en 2026

Kernels, sistemas embebidos, runtimes de otros lenguajes y bases de datos: el mapa real de dónde se ejecuta C hoy y por qué su ABI sigue siendo la lingua franca de los sistemas.

⏱ 13 min

Preguntar si C sigue vivo es la pregunta equivocada. La pregunta correcta es qué capa del sistema ocupa, y la respuesta incomoda: C ya casi no es el lenguaje en el que se escriben aplicaciones, pero es el lenguaje en el que está escrito casi todo aquello sobre lo que se ejecutan las aplicaciones. Vive debajo, y debajo no se cambia con facilidad.

🎯 Al terminar esta lección sabrás
  • Localizar C en las cuatro capas donde realmente domina hoy.
  • Entender por qué el embebido y la certificación lo mantienen insustituible.
  • Ver cuántos lenguajes de alto nivel son, literalmente, programas en C.
  • Explicar por qué la ABI de C es el protocolo común de la industria.

El sustrato: núcleos y sistemas operativos

Todo sistema operativo de propósito general que uses en 2026 tiene un núcleo escrito mayoritariamente en C. Linux ronda las decenas de millones de líneas; el núcleo NT de Windows, el XNU de macOS y iOS, y los BSD comparten el mismo linaje técnico. Por debajo del escritorio, los sistemas de tiempo real que gobiernan maquinaria industrial y automoción siguen la misma regla.

La entrada de Rust en el núcleo de Linux es real y significativa —hay controladores en producción y el soporte está consolidado—, pero conviene leerla con precisión: Rust entra en las hojas, no en el tronco. El planificador, el gestor de memoria, el subsistema de interrupciones y la capa de arquitectura siguen siendo C, y lo seguirán siendo durante décadas, porque reescribirlos no es un problema de lenguaje sino de riesgo acumulado.

Esa asimetría define la posición de C hoy: no compite por el código nuevo, ocupa el código que no se puede volver a escribir.

Merece la pena precisar por qué el tronco es tan resistente. Un núcleo no es una aplicación grande: es un programa sin biblioteca estándar por debajo, que se ejecuta sin red de seguridad, que debe respetar contratos binarios con el hardware y que mantiene invariantes globales entre subsistemas escritos por miles de personas a lo largo de treinta años. Sustituir una pieza de ese tejido no aporta seguridad si la pieza vecina sigue asumiendo las reglas antiguas.

ℹ️
Cómo leer la entrada de Rust en el núcleo

El soporte de Rust en Linux es de primera clase y hay controladores en producción, pero la regla de convivencia es explícita: los enlaces con el resto del núcleo se expresan sobre interfaces de C, y el código nuevo debe respetar los mismos contratos de bloqueo, contextos atómicos y gestión de referencias que el código existente. Es decir, para escribir Rust de núcleo hay que entender la semántica de C del núcleo. La sustitución del lenguaje no cancela el modelo mental.

Embebido y tiempo real: donde no hay alternativa

En microcontroladores el argumento cambia de naturaleza. No es inercia: es que el resto de opciones a menudo no existe sobre ese silicio.

⚙️

Disponibilidad del compilador

Todo fabricante de silicio entrega un compilador de C con su chip. Es la única garantía transversal: una arquitectura nueva tiene C antes que ninguna otra cosa.

📏

Normas de sector

MISRA C en automoción, DO-178C en aviónica, IEC 62304 en dispositivos médicos. Los subconjuntos disciplinados de C llevan décadas de jurisprudencia técnica detrás.

🔬

Cadenas cualificadas

Certificar un sistema crítico exige un compilador cualificado con evidencia documental. Ese ecosistema existe para C desde hace treinta años.

🪶

Coste en recursos

Con kilobytes de RAM y sin sistema operativo, el modelo de ejecución de C —sin runtime, sin recolector, sin metadatos— sigue siendo el punto de partida natural.

Hay además una razón estructural que se ve mejor con código. En un microcontrolador, hablar con un periférico consiste en escribir en una dirección física concreta; C expresa eso directamente, sin capa intermedia ni permiso de nadie:

#include <stdint.h>

/* Registro de control de un periferico, mapeado en memoria */
#define GPIO_BASE   0x40020000u
#define GPIO_ODR    (*(volatile uint32_t *)(GPIO_BASE + 0x14))

void led_encender(unsigned pin)
{
    GPIO_ODR |= (1u << pin);   /* volatile: el compilador no puede eliminar ni reordenar */
}

El calificador volatile es la pieza clave: le comunica al optimizador que ese acceso tiene efectos observables fuera del modelo abstracto del lenguaje. Sin él, una escritura cuyo valor nunca se lee sería eliminada como código muerto, con toda la razón formal del mundo y con consecuencias desastrosas en el dispositivo.

El matiz honesto: las ventajas de las tarjetas anteriores son de ecosistema, no del lenguaje en sí. A medida que otras cadenas de herramientas obtienen cualificación y cubren más arquitecturas, este territorio se estrecha. Pero se estrecha en años, no en trimestres, porque cada paso exige rehacer evidencia de certificación que ya estaba pagada.

El runtime de todos los demás

Aquí está el dato que más desconcierta a quien viene de lenguajes de alto nivel: muchos lenguajes que se perciben como alternativas a C son, en su implementación de referencia, programas escritos en C.

  • El intérprete de referencia de Python, el de Ruby, el motor de PHP y el de Lua son ejecutables en C.
  • La biblioteca de entrada y salida asíncrona que sostiene buena parte del ecosistema JavaScript de servidor está escrita en C.
  • Bases de datos y servidores que sostienen internet: el motor embebido más desplegado del mundo, los principales gestores relacionales, los almacenes en memoria, los servidores web de alto rendimiento.
  • La infraestructura invisible: la biblioteca de compresión que usa casi todo formato, las pilas criptográficas, los códecs multimedia, el cliente HTTP que está dentro de casi cualquier dispositivo.

La consecuencia práctica es fuerte: cuando escribes una extensión nativa para tu lenguaje favorito, cuando depuras una fuga de memoria en producción o cuando lees un volcado de pila que cruza la frontera del intérprete, estás leyendo C, aunque no hayas escrito ni una línea.

Y la frontera es más exigente de lo que parece, porque en ella conviven dos modelos de gestión de memoria incompatibles. El intérprete cuenta referencias o recolecta; tu código en C no. La forma canónica de una función de extensión revela el problema:

/* Firma tipica de una extension nativa: recibe y devuelve objetos del interprete */
static PyObject *mi_funcion(PyObject *self, PyObject *args)
{
    const char *texto;
    if (!PyArg_ParseTuple(args, "s", &texto))
        return NULL;              /* propagar el error del interprete */

    /* aqui mandas tu: memoria manual, sin recolector que te cubra */

    return PyUnicode_FromString(texto);   /* devuelve una nueva referencia */
}

Cada comentario de ese fragmento esconde una obligación contractual: quién posee la referencia devuelta, quién la libera, qué pasa si tu código lanza un error a mitad. Equivocarte ahí no produce una excepción: produce una fuga silenciosa o un fallo de segmentación en un proceso que llevaba semanas en producción.

Por qué es la lingua franca: la ABI

La razón profunda de la permanencia de C no está en su sintaxis ni en su rendimiento, sino en un accidente afortunado: la interfaz binaria de aplicación de cada plataforma está descrita en términos de C. La convención de llamada de un procesador, el formato de los ejecutables, la interfaz de llamadas al sistema del núcleo: todo se documenta con tipos y funciones de C.

Eso convierte a C en el mínimo común denominador de la interoperabilidad. Cuando dos lenguajes cualesquiera necesitan hablarse, la traducción pasa por ahí: la palabra clave de enlace extern "C" en C++, las anotaciones repr(C) en Rust, los mecanismos de invocación nativa de la máquina virtual de Java, las interfaces de plataforma de .NET, los módulos nativos de los intérpretes dinámicos.

flowchart TD
P[Python] --> ABI[ABI de C]
R[Rust] --> ABI
G[Go] --> ABI
J[Java y dotnet] --> ABI
Z[Zig] --> ABI
W[WebAssembly] --> ABI
ABI --> LIB[Bibliotecas de sistema y libc]
LIB --> SYS[Llamadas al sistema]
SYS --> K[Nucleo del sistema operativo]
K --> HW[Hardware]
style ABI fill:#f9e2af,color:#11111b
style K fill:#89b4fa,color:#11111b
style HW fill:#cba6f7,color:#11111b

Fíjate en la forma del diagrama: no es que todo el mundo use C, es que todo el mundo converge en él para cruzar fronteras. C no ganó la discusión sobre lenguajes; ganó la de los protocolos.

Por eso una biblioteca pensada para ser consumida desde cualquier parte se diseña con una forma muy reconocible: tipos opacos, funciones simples, propiedad de la memoria declarada sin ambigüedad.

/* Cabecera pensada como frontera binaria estable */
typedef struct motor motor;              /* tipo opaco: el cliente no ve el layout */

motor      *motor_crear(const char *config);
int         motor_procesar(motor *m, const uint8_t *datos, size_t n);
const char *motor_ultimo_error(const motor *m);   /* propiedad: de la biblioteca */
void        motor_destruir(motor *m);             /* propiedad: la cede el cliente */

Nada de esa cabecera depende de C como lenguaje de implementación: el interior puede estar escrito en C, en Rust o en Zig. Lo que importa es que la interfaz está expresada en el vocabulario que toda la industria sabe leer. Un envoltorio para un intérprete dinámico, un enlace para una máquina virtual o un módulo de otro lenguaje compilado se generan casi mecánicamente a partir de ella.

C no sobrevive por inercia: sobrevive por ser el punto fijo del sistema

Hay una lectura perezosa según la cual C persiste porque nadie se ha molestado en reemplazarlo. Es falsa, y entender por qué te reordena el mapa mental entero. C ocupa una posición estructural: es simultáneamente el lenguaje del núcleo, el lenguaje de la biblioteca del sistema y el lenguaje de la interfaz binaria. Reemplazarlo en cualquiera de esas tres capas no es escribir código nuevo, es renegociar el contrato con todas las demás capas a la vez. Por eso Rust entra en los controladores y no en el planificador; por eso los lenguajes seguros describen su interoperabilidad en términos de C en lugar de proponer la suya; por eso incluso los objetivos de compilación más modernos reproducen convenciones de llamada heredadas. La implicación para ti es directa y práctica: aprender C en 2026 no es apostar a que escribirás sistemas enteros en C —probablemente no lo harás—, es adquirir la capacidad de leer, depurar y definir la frontera que todos los demás lenguajes están obligados a atravesar. Quien no sabe leer esa frontera trabaja con una venda puesta en cuanto algo baja de su capa habitual.

⚔️ Encuentra el C que ya estás ejecutando
  1. Elige un proceso cualquiera de tu máquina e inspecciona sus bibliotecas dinámicas: identifica cuáles están escritas en C.
  2. Localiza en tu sistema la biblioteca estándar de C y lista algunos de sus símbolos exportados.
  3. Toma un módulo nativo de tu lenguaje de alto nivel favorito y busca su fuente: confirma que es C y localiza la función que expone la interfaz.
  4. Ejecuta un programa trivial bajo un trazador de llamadas al sistema y observa cuántas ocurren antes de que empiece tu código.
  5. Escribe en tres líneas por qué la ABI, y no el rendimiento, es el argumento más fuerte de esta lección.