Menu

El preprocesador de C: #include, #define y lo que se ejecuta antes de compilar

El preprocesador edita el texto de tu código fuente antes de que el compilador lo vea: pega cabeceras, sustituye macros y activa o desactiva código. Aquí está toda la familia de directivas y cómo mirar su salida.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

Todos los archivos de C que has compilado empezaban con una línea como #include <stdio.h>, y esa línea no es C. Es una instrucción para un programa aparte —el preprocesador— que se ejecuta primero, reescribe el texto de tu fuente y le entrega el resultado al compilador.

Entender esa división en dos etapas explica mucho del comportamiento de C: por qué las cabeceras se pegan en lugar de importarse, por qué una macro mala produce un error en una línea que se ve perfectamente bien y por qué el mismo archivo fuente puede compilar a programas distintos en sistemas distintos.

Qué pasa realmente antes de compilar

Compilar un archivo .c no es un solo paso. A grandes rasgos son cuatro:

  1. Preprocesado: obedecer cada línea que empieza con #, produciendo un gran texto fuente expandido (una unidad de traducción).
  2. Compilación: convertir ese texto en ensamblador y luego en un archivo objeto.
  3. Ensamblado: producir código máquina.
  4. Enlace: unir los archivos objeto con las bibliotecas en un ejecutable.

El preprocesador trabaja puramente sobre texto. No sabe qué es una variable, qué es un tipo ni si tus llaves están equilibradas. Ve caracteres y tokens, reemplaza unos por otros y sigue adelante. Todo lo extraño de las macros se deriva de ese único hecho.

Para cuando el compilador lee este programa, no hay ningún GREETING por ninguna parte. El preprocesador ya lo reemplazó por el literal "Hola desde una macro", exactamente como si lo hubieras escrito tú.

La familia de directivas

Toda directiva del preprocesador empieza con # como primer carácter no blanco de su línea. No lleva punto y coma, y la directiva termina al final de la línea salvo que la continúes con una barra invertida.

DirectivaQué hace
#includePega el contenido de otro archivo
#defineDefine una macro (una sustitución de texto)
#undefElimina la definición de una macro
#ifdef, #ifndefConserva el código siguiente solo si una macro está (o no) definida
#if, #elif, #else, #endifConserva código según una expresión constante
#errorDetiene la compilación con un mensaje
#pragmaInstrucción específica del compilador, como #pragma once
#lineCambia el número de línea reportado (poco común)

Tres de estas tienen su propia página: macros cubre #define a fondo, compilación condicional cubre la familia #if y archivos de cabecera cubre cómo se usa #include para estructurar un programa de varios archivos.

#include: ángulos frente a comillas

#include hace exactamente una cosa: reemplaza su propia línea por el contenido entero del archivo nombrado. Ese archivo incluido se preprocesa a su vez, así que sus propias líneas #include también se expanden.

#include <stdio.h>     /* buscar en los directorios de inclusion del sistema */
#include "config.h"    /* buscar primero en el directorio de este archivo */

La diferencia es el orden de búsqueda:

  • <ángulos> miran en los directorios de inclusión estándar del compilador —/usr/include, las carpetas propias de la cadena de herramientas, más lo que agregues con -I—. Esto es para cabeceras de bibliotecas.
  • "comillas" miran primero en el directorio del archivo que hace la inclusión y luego recurren a la misma lista que los ángulos. Esto es para cabeceras que escribiste tú.

Ambas formas funcionan con cualquiera de los dos tipos de cabecera en la mayoría de los compiladores, pero la convención tiene significado: los ángulos dicen "esta cabecera es de otra persona", las comillas dicen "esta es mía". Confundirlas es como un proyecto termina incluyendo una cabecera rancia del sistema en lugar de la suya.

Como la inclusión es un pegado textual, incluir dos veces la misma cabecera la pega dos veces, y por eso las cabeceras necesitan guardianes de inclusión. Eso es lo primero que arregla la página de archivos de cabecera.

#define: sustitución de texto y nada más

#define NOMBRE reemplazo le dice al preprocesador: de aquí en adelante, dondequiera que aparezca el token NOMBRE, pon reemplazo en su lugar.

Fíjate en lo que no está pasando aquí. MAX_USERS no tiene tipo. No se guarda en ninguna parte. No se puede inspeccionar en un depurador. Es una regla de buscar y reemplazar, y tras el preprocesado el programa contiene literalmente printf("%s admite %d usuarios\n", "Coddy", 100);.

Eso también significa que la sustitución es ciega. Esto compila y hace algo sorprendente:

#define SIZE 5 + 1

int arr[SIZE];          /* bien: int arr[5 + 1]; */
int total = SIZE * 2;   /* 5 + 1 * 2 == 7, no 12 */

La solución —paréntesis alrededor de todo— es la regla central de la página de macros, junto con las macros que toman argumentos.

Un #define sin texto de reemplazo define el nombre como "presente pero vacío". Eso es inútil como sustitución e imprescindible como bandera:

#define DEBUG          /* definido, se expande a nada */

El código puede entonces preguntar si DEBUG existe con #ifdef.

Ver la salida del preprocesador

La mejor forma de construir intuición es mirar lo que el preprocesador produjo de verdad. gcc -E se detiene tras el preprocesado e imprime el resultado:

gcc -E hello.c

Para un archivo que incluye <stdio.h>, eso son de 700 a 30.000 líneas según tu sistema, casi todas ellas el contenido de la propia cabecera. Para ver solo tu parte, toma la cola:

gcc -E hello.c | tail -20

Pruébalo con un archivo como este:

#define SQUARE(x) ((x) * (x))
#define LIMIT 10

int main(void) {
    int n = SQUARE(LIMIT);
    return n;
}

La cola de la salida muestra:

int main(void) {
    int n = ((10) * (10));
    return n;
}

Todas las macros han desaparecido; solo queda el texto sustituido. Cuando una macro se porta mal, este comando te dice por qué en segundos, y le gana a adivinar. Dos compañeros útiles: gcc -dM -E - < /dev/null lista todas las macros que tu compilador predefine, y gcc -E -P archivo.c omite el ruido de los marcadores de línea.

Por qué los errores señalan la línea equivocada

Como el compilador ve el texto expandido, un error dentro de una macro se reporta donde la macro fue usada, no donde fue escrita:

#define HALF(x) (x / 2

int main(void) {
    int y = HALF(8);   /* el error se reporta aqui */
    return 0;
}

El paréntesis que falta está en la línea del #define, pero el compilador se queja de la línea que contiene HALF(8), a menudo con un mensaje sobre un token inesperado que no tiene ningún sentido en contexto. Cuando un error parece imposible, expande el archivo con gcc -E y lee la línea real.

Los compiladores modernos ayudan: GCC y clang imprimen una nota de "in expansion of macro" que apunta de vuelta a la definición. Compila con -Wall -Wextra para ver de verdad esas notas.

Macros predefinidas

El preprocesador aporta algunas macros por su cuenta. Son genuinamente útiles para diagnósticos:

__FILE__ y __LINE__ se expanden al nombre del archivo actual y al número de línea, que es como las macros de aserción y de registro reportan dónde salió algo mal. (__func__ es ligeramente distinto: es un identificador real que aporta el compilador, no una macro del preprocesador, pero se usa igual.)

Los compiladores también predefinen macros de plataforma como __linux__, _WIN32 y __APPLE__. El código que debe diferir según el sistema las comprueba con #ifdef, que es el tema de la compilación condicional.

Qué llevarse

El preprocesador es pequeño y es tonto, y ambas cosas son a propósito. Te da tres poderes —traer un archivo, sustituir texto y activar o desactivar código— y ninguna comprobación de tipos que los acompañe.

Ese intercambio es por lo que el consejo estándar es recurrir primero a las características del lenguaje: usa const int o un enum en lugar de #define para las constantes cuando puedas, y una función de verdad en lugar de una macro tipo función. Donde el preprocesador es genuinamente la herramienta correcta —cabeceras, interruptores de portabilidad, configuración en tiempo de compilación— es irremplazable.

A continuación, macros se toma en serio el #define: argumentos, las reglas de los paréntesis y las trampas que vienen con sustituir texto dentro de código que no escribiste.

Preguntas frecuentes

¿Qué es el preprocesador en C?

Un paso de procesamiento de texto que se ejecuta antes de la compilación. Obedece las líneas que empiezan con #: pega el contenido de los archivos de cabecera en lugar del #include, sustituye las macros definidas con #define y borra o conserva código según #if/#ifdef. El compilador solo ve el resultado, nunca tu archivo original.

¿Cuál es la diferencia entre #include <stdio.h> e #include "miarchivo.h"?

Los ángulos buscan en los directorios de inclusión del sistema del compilador, que es donde viven las cabeceras de la biblioteca estándar. Las comillas buscan primero en el directorio del archivo actual y luego recurren a las rutas del sistema. Usa ángulos para las cabeceras de bibliotecas y comillas para las que escribiste tú.

¿Cómo puedo ver lo que produjo el preprocesador?

Ejecuta gcc -E archivo.c para detenerte tras el preprocesado e imprimir el fuente expandido. En un archivo que incluye <stdio.h> la salida son miles de líneas, así que canalízala: gcc -E archivo.c | tail -30 muestra solo tu propio código con cada macro ya sustituida.

¿#include es una sentencia de C?

No. Las directivas no son parte del lenguaje C propiamente dicho: tienen su propia sintaxis basada en líneas, no llevan punto y coma y ya no existen cuando el compilador analiza tu programa. Por eso un error en una macro aparece como un error confuso en una línea que se ve bien.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR