Lección 15 · 15 de 20
El preprocesador · C
Use el preprocesador de C: constantes y macros con parámetros, # y ##, compilación condicional con #if y guardas de inclusión en las cabeceras.

Antes de compilar, el preprocesador trata las líneas que empiezan por # y trabaja solo con texto. #define VAT_RATE 0.21 hace que cada VAT_RATE del código se sustituya por 0.21: no es una variable, no ocupa memoria y no tiene tipo.
Las macros con parámetros también son sustitución de texto, y eso tiene trampa. SQUARE_BAD(1 + 2) se convierte en 1 + 2 * 1 + 2, que vale 5 y no 9. La versión correcta pone paréntesis a cada parámetro y al resultado entero: ((x) * (x)). Aun así, un argumento como i++ se evaluaría dos veces; para algo más complejo es mejor una función.
`#` convierte un argumento en texto: SHOW(expr) escribe la propia expresión y su valor. `##` pega dos piezas en un solo nombre: FIELD(stock) declara item_stock. Las macros predefinidas __FILE__, __LINE__ y __STDC_VERSION__ dan el archivo, la línea y la versión de C (201112 es C11).
`#if` compila un trozo u otro según una condición que se resuelve antes de compilar: con DEBUG a 1 entra el mensaje de depuración y el #else ni siquiera llega al compilador. Normalmente DEBUG se define al compilar, con -DDEBUG=1, sin tocar el código.
La guarda de inclusión rodea todo el contenido de una cabecera .h: la primera vez SHOP_H no está definida, se define y entra el contenido; si la cabecera se incluye otra vez, #ifndef SHOP_H es falso y se salta. El ejemplo lo muestra con el mismo struct item escrito dos veces: sin la guarda, sería un error por definirlo dos veces.
Código · main.c
#include <stdio.h>// Include guard: normally the whole body of a header file such as shop.h.#ifndef SHOP_H // first time: SHOP_H is not defined yet#define SHOP_Hstruct item { const char *name; double price; };#endif#ifndef SHOP_H // shop.h included again: now it is skipped,struct item { const char *name; double price; }; // or this would be an error#endif#define VAT_RATE 0.21 // replaced by its text before compiling#define SQUARE_BAD(x) x * x // no parentheses: a classic bug#define SQUARE(x) ((x) * (x))#define MAX(a, b) ((a) > (b) ? (a) : (b))#define SHOW(expr) printf("%-17s = %g\n", #expr, (double)(expr)) // # makes text#define FIELD(name) item_##name // ## glues tokens#define DEBUG 1int main(void) { struct item tea = {"Green tea", 4.50}; printf("%s with VAT: %.2f\n", tea.name, tea.price * (1 + VAT_RATE)); SHOW(SQUARE_BAD(1 + 2)); // becomes 1 + 2 * 1 + 2 SHOW(SQUARE(1 + 2)); SHOW(MAX(7, 12)); int FIELD(stock) = 40; // declares item_stock printf("item_stock = %d\n", item_stock);#if DEBUG printf("[debug] %s, line %d, C standard %ld\n", __FILE__, __LINE__, __STDC_VERSION__);#else printf("Release build\n");#endif return 0;}Salida · cc main.c -o main && ./main
Green tea with VAT: 5.45 SQUARE_BAD(1 + 2) = 5 SQUARE(1 + 2) = 9 MAX(7, 12) = 12 item_stock = 40 [debug] main.c, line 29, C standard 201112
Para recordar
#definesustituye texto antes de compilar.- En las macros, paréntesis en cada parámetro y en el total.
#convierte en texto y##une nombres.- Toda cabecera lleva
#ifndef,#definey#endif.
Ejercicio
Cambie DEBUG a 0 y compruebe la salida; después escriba la macro CLAMP(x, lo, hi), que deja x entre lo y hi, y pruébela con SHOW.