DMarlon Studio

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.

C · El preprocesador en DMarlon Studio: el código del ejemplo y su salida en la terminal

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

  • #define sustituye 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, #define y #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.