El preprocesador en C — ejercicios con el aforo de un ascensor, un limitador de plantas y un simulador con trazas (y el DEBUG 0 que no apagaba nada)

Después de aquel cero por entregar la práctica llena de trazas, me hice muy disciplinado: arriba del todo puse #define DEBUG 1 y todas las trazas dentro de un #ifdef DEBUG. Para la entrega, cambié el 1 por un 0, convencido de que así quedaban apagadas. Ejecuté el programa la noche antes de entregar y… ahí seguían todas, una detrás de otra. #ifdef no pregunta si vale 1 o 0: pregunta si existe. Y existía.

Estos ejercicios son para que a ti no te pase. Vas a escribir tus propias macros, a pelearte con las trampas del preprocesador y a montar un pequeño simulador con cabeceras y trazas que se encienden con -DDEBUG. Si te atascas, la teoría y la práctica tienen todo lo que hace falta.

Cómo usar estos ejercicios

  1. Lee el enunciado y la salida esperada, y escribe tu solución sin mirar las soluciones.
  2. Compila siempre con gcc -Wall -std=c11: en este tema los avisos del compilador te van a chivar más de un fallo de macro.
  3. Si no sale, abre la 🔵 pista y, si sigue sin salir, mira tu código con gcc -E -P para ver qué ha hecho el preprocesador.
  4. Cuando funcione, compara con la solución: lo importante es entender por qué está hecha así, sobre todo los paréntesis.

Nivel 1: La calculadora de aforo

Escribe un programa con estas constantes: CARGA_MAXIMA 1500, PESO_ADULTO 75, PESO_NINO 35 y NUM_GRUPOS 4. Define dos macros:

  • PESO_GRUPO(adultos, ninos): el peso del grupo.
  • CABE(kg): cierto si el ascensor aguanta esos kilos (1500 kg exactos todavía caben).

Los grupos tienen {4, 15, 20, 16} adultos y {2, 6, 0, 10} niños. Para cada grupo, imprime su peso, si cabe y, como prueba de fuego para tus paréntesis, el peso y el resultado si subiera un adulto más, calculado con PESO_GRUPO(adultos[i] + 1, ninos[i]).

Salida esperada:

Grupo 1 ( 4+ 2):  370 kg cabe      | con un adulto más:  445 kg cabe
Grupo 2 (15+ 6): 1335 kg cabe      | con un adulto más: 1410 kg cabe
Grupo 3 (20+ 0): 1500 kg cabe      | con un adulto más: 1575 kg NO cabe
Grupo 4 (16+10): 1550 kg NO cabe   | con un adulto más: 1625 kg NO cabe

Si la columna «con un adulto más» te sale con cifras raras (por ejemplo, 149 en vez de 445 en el primer grupo), el + 1 se está sumando sin multiplicar. Revisa los paréntesis de los parámetros. Para alinear «cabe» y «NO cabe» usa %-9s, que rellena con espacios por la derecha hasta 9 caracteres.

Nivel 2: El limitador de plantas

El edificio va de la planta −2 a la 8, pero los botones del panel a veces mandan pedidos imposibles. Define las macros MIN(a, b), MAX(a, b), LIMITAR(x, lo, hi) (que deja x dentro del rango [lo, hi]) y ABS(x). La cabina empieza en la planta 0 y recibe los pedidos {5, 12, -4, 0, 8, -2, 9}. Para cada uno, la cabina va a la planta limitada; marca con «(corregido)» los que se han tenido que cambiar. Al final, cuenta los pedidos corregidos y las plantas recorridas en total.

Salida esperada:

Pedido   5 -> va a  5
Pedido  12 -> va a  8 (corregido)
Pedido  -4 -> va a -2 (corregido)
Pedido   0 -> va a  0
Pedido   8 -> va a  8
Pedido  -2 -> va a -2
Pedido   9 -> va a  8 (corregido)
Corregidos: 3 | Plantas recorridas: 48

LIMITAR se puede escribir con las otras dos macros: primero se sube el valor al mínimo con MAX y luego se baja al máximo con MIN. Y no le pases pedidos[i++] a ninguna macro: copia antes el valor en una variable.

Nivel 3: El simulador del día

Ahora con cabeceras. Crea un ascensor.h, con guarda, que tenga CARGA_MAXIMA 1500, PESO_MEDIO 75, PERSONAS_MAX (calculada), las macros ABS, MIN y DIV_ARRIBA(a, b) (división entera redondeando hacia arriba) y un struct Grupo con origen, destino y personas. Crea también un registro.h, con guarda, que incluya ascensor.h y defina LOG(msg, valor): con -DDEBUG imprime [archivo:línea] msg = valor; sin él, no hace nada.

El programa simulador.c incluye los dos .h y simula estos cinco grupos: {0, 6, 8}, {0, 3, 25}, {4, -1, 12}, {7, 0, 41}, {-2, 5, 3}. Si un grupo no cabe de una vez, el ascensor hace varios viajes, y entre viaje y viaje vuelve vacío a por más gente. Para cada grupo, calcula los viajes y las plantas recorridas, con un LOG de cada valor; al final, los totales y el viaje más lleno del día.

Salida esperada sin -DDEBUG:

Grupo 1:  8 pers.  0 ->  6 | 1 viaje(s),  6 plantas
Grupo 2: 25 pers.  0 ->  3 | 2 viaje(s),  9 plantas
Grupo 3: 12 pers.  4 -> -1 | 1 viaje(s),  5 plantas
Grupo 4: 41 pers.  7 ->  0 | 3 viaje(s), 35 plantas
Grupo 5:  3 pers. -2 ->  5 | 1 viaje(s),  7 plantas
Total: 8 viajes, 62 plantas, máximo 20 personas (1500 kg) a la vez

Y con gcc -Wall -std=c11 -DDEBUG simulador.c -o simulador:

  [simulador.c:20] viajes = 1
  [simulador.c:21] plantas = 6
Grupo 1:  8 pers.  0 ->  6 | 1 viaje(s),  6 plantas
  [simulador.c:20] viajes = 2
  [simulador.c:21] plantas = 9
Grupo 2: 25 pers.  0 ->  3 | 2 viaje(s),  9 plantas
  [simulador.c:20] viajes = 1
  [simulador.c:21] plantas = 5
Grupo 3: 12 pers.  4 -> -1 | 1 viaje(s),  5 plantas
  [simulador.c:20] viajes = 3
  [simulador.c:21] plantas = 35
Grupo 4: 41 pers.  7 ->  0 | 3 viaje(s), 35 plantas
  [simulador.c:20] viajes = 1
  [simulador.c:21] plantas = 7
Grupo 5:  3 pers. -2 ->  5 | 1 viaje(s),  7 plantas
Total: 8 viajes, 62 plantas, máximo 20 personas (1500 kg) a la vez

DIV_ARRIBA(41, 20) tiene que dar 3. El truco clásico es sumar b - 1 antes de dividir: (41 + 19) / 20. Y si haces n viajes, el ascensor recorre el tramo 2n − 1 veces: n idas con gente y n − 1 vueltas vacías.

¿Qué falla aquí?

Tres fragmentos que compilan (alguno con avisos) y hacen algo distinto de lo que parece. Antes de leer la explicación, predice qué imprimen.

Fragmento 1: el ! que no niega

#include <stdio.h>

#define CARGA_MAXIMA 1500
#define DEMASIADO_GRANDE(v) v >= CARGA_MAXIMA

int main(void) {
    int cargas[] = {300, 1650, 980};
    for (int i = 0; i < 3; i++) {
        if (!DEMASIADO_GRANDE(cargas[i]))
            printf("%d kg: puede subir\n", cargas[i]);
        else
            printf("%d kg: demasiado peso\n", cargas[i]);
    }
    return 0;
}

¿Qué crees que sale para 300, 1650 y 980 kg?

Esto es lo que sale de verdad:

300 kg: demasiado peso
1650 kg: demasiado peso
980 kg: demasiado peso

Las tres cargas son «demasiado peso», hasta la de 300 kg. Y eso que gcc -Wall avisó al compilar (te enseño los avisos sin el número de columna):

fragmento.c: In function 'main':
fragmento.c:4: warning: comparison of constant '1500' with boolean expression is always false [-Wbool-compare]
fragmento.c:9: note: in expansion of macro 'DEMASIADO_GRANDE'
fragmento.c:4: warning: logical not is only applied to the left hand side of comparison [-Wlogical-not-parentheses]
fragmento.c:9: note: in expansion of macro 'DEMASIADO_GRANDE'
fragmento.c:9: note: add parentheses around left hand side expression to silence this warning

El preprocesador convierte !DEMASIADO_GRANDE(cargas[i]) en !cargas[i] >= CARGA_MAXIMA, y el ! tiene más prioridad que >=. Así que C calcula (!cargas[i]) >= 1500: !300 vale 0 (cualquier número distinto de 0 negado da 0), y 0 >= 1500 es falso. El if nunca se cumple y todo va al else. El dibujo enseña los dos caminos:

El ! que se come la macro Arriba, lo esperado: 300 >= 1500 es 0, y !0 es 1, puede subir. Abajo, lo real: !300 es 0, 0 >= 1500 es 0, demasiado peso. !DEMASIADO_GRANDE(300) sin paréntesis Lo que crees que pone: !(v >= 1500) 300 >= 1500 0 !0 = 1 puede subir Lo que entiende C: (!v) >= 1500 !300 0 0 >= 1500 0 demasiado peso El ! tiene más prioridad que >=: se aplica solo a v, y !300 vale 0 para cualquier carga distinta de 0.
Con v = 300 el camino de abajo da 0 siempre: por eso las tres cargas salen como «demasiado peso».

Fiarse de que la macro «es una condición». Sin paréntesis exteriores, cualquier operador que pongas delante (un !, un -, un *) se pega solo al primer trozo. Los avisos -Wlogical-not-parentheses y -Wbool-compare te lo están diciendo.

El arreglo es poner la macro entera, y su parámetro, entre paréntesis:

#include <stdio.h>

#define CARGA_MAXIMA 1500
#define DEMASIADO_GRANDE(v) ((v) >= CARGA_MAXIMA)

int main(void) {
    int cargas[] = {300, 1650, 980};
    for (int i = 0; i < 3; i++) {
        if (!DEMASIADO_GRANDE(cargas[i]))
            printf("%d kg: puede subir\n", cargas[i]);
        else
            printf("%d kg: demasiado peso\n", cargas[i]);
    }
    return 0;
}

Y ahora sí:

300 kg: puede subir
1650 kg: demasiado peso
980 kg: puede subir

Ante un aviso que dice «in expansion of macro», el fallo casi nunca está en la línea del if, sino en el #define. Ve a la línea que indica el primer aviso (aquí, la 4).

Fragmento 2: el punto y coma que se cuela

#include <stdio.h>

#define CARGA_MAXIMA 1500;

int main(void) {
    int carga = 640;
    int libre = CARGA_MAXIMA - carga;
    printf("Quedan %d kg libres\n", libre);
    return 0;
}

Si hay 640 kg dentro, ¿cuántos quedan libres?

Lo que sale:

Quedan 1500 kg libres

1500 kg libres con 640 kg dentro. El aviso del compilador da la pista:

fragmento.c: In function 'main':
fragmento.c:7: warning: statement with no effect [-Wunused-value]

El ; del #define es parte del texto de sustitución, así que la línea 7 queda como int libre = 1500; - carga;. Son dos sentencias: int libre = 1500; y - carga;, que calcula −640 y lo tira a la basura («statement with no effect»). libre se queda en 1500.

Terminar un #define con punto y coma por costumbre. Unas veces da un error de sintaxis y te enteras; otras, como aquí, compila y el resultado está mal.

El arreglo es quitar el ;:

#include <stdio.h>

#define CARGA_MAXIMA 1500

int main(void) {
    int carga = 640;
    int libre = CARGA_MAXIMA - carga;
    printf("Quedan %d kg libres\n", libre);
    return 0;
}

Ahora int libre = 1500 - carga; hace la cuenta entera:

Quedan 860 kg libres

1500 − 640 = 860.

Los avisos -Wunused-value («statement with no effect») en una línea que usa un #define casi siempre son un ; de más en la definición.

Fragmento 3: el DEBUG que vale 0

#include <stdio.h>

#define DEBUG 0     /* 0 = depuración apagada... ¿seguro? */

int main(void) {
    int carga = 640;
#ifdef DEBUG
    printf("[DEBUG] carga = %d\n", carga);
#endif
    printf("Carga: %d kg\n", carga);
    return 0;
}

Con DEBUG a 0, ¿sale la traza?

Sale:

[DEBUG] carga = 640
Carga: 640 kg

Es mi anécdota. #define DEBUG 0 define el símbolo DEBUG, aunque su valor sea 0. Y #ifdef DEBUG solo pregunta si existe, así que la traza pasa al compilador. Aquí ni siquiera hay aviso: para el preprocesador está todo correcto.

Usar #ifdef con un símbolo que tiene valor 0 o 1. O lo defines y lo quitas (#ifdef + -DDEBUG), o le das valor y lo comparas (#if DEBUG). Mezclar las dos ideas no funciona.

El arreglo, si quieres mantener el 0 y el 1, es preguntar por el valor con #if:

#include <stdio.h>

#define DEBUG 0     /* 0 = apagada, 1 = encendida */

int main(void) {
    int carga = 640;
#if DEBUG
    printf("[DEBUG] carga = %d\n", carga);
#endif
    printf("Carga: %d kg\n", carga);
    return 0;
}

Y la traza desaparece:

Carga: 640 kg

Con #if DEBUG, si un día borras el #define DEBUG el preprocesador lo trata como 0 y las trazas se apagan solas. Es la versión más segura de las dos.

Soluciones

Solución del Nivel 1

#include <stdio.h>

#define CARGA_MAXIMA 1500
#define PESO_ADULTO 75
#define PESO_NINO 35
#define NUM_GRUPOS 4

/* Paréntesis en cada parámetro: así PESO_GRUPO(a + 1, n) sigue funcionando */
#define PESO_GRUPO(adultos, ninos) ((adultos) * PESO_ADULTO + (ninos) * PESO_NINO)
#define CABE(kg) ((kg) <= CARGA_MAXIMA)    /* <= : justo 1500 kg todavía cabe */

int main(void) {
    int adultos[NUM_GRUPOS] = {4, 15, 20, 16};
    int ninos[NUM_GRUPOS]   = {2, 6, 0, 10};

    for (int i = 0; i < NUM_GRUPOS; i++) {
        int kg = PESO_GRUPO(adultos[i], ninos[i]);
        int con_uno_mas = PESO_GRUPO(adultos[i] + 1, ninos[i]);   /* prueba de fuego */
        printf("Grupo %d (%2d+%2d): %4d kg %-9s | con un adulto más: %4d kg %s\n",
               i + 1, adultos[i], ninos[i], kg, CABE(kg) ? "cabe" : "NO cabe",
               con_uno_mas, CABE(con_uno_mas) ? "cabe" : "NO cabe");
    }
    return 0;
}

Por qué está hecho así:

  • PESO_GRUPO lleva paréntesis en (adultos) y en (ninos). Con PESO_GRUPO(adultos[i] + 1, ninos[i]), sin ellos quedaría adultos[i] + 1 * 75 + ninos[i] * 35: solo el adulto extra se multiplicaría por 75 y el primer grupo pesaría 4 + 75 + 70 = 149 kg en vez de 445. La columna de la derecha es la que lo comprueba.
  • La expresión entera va entre paréntesis por si alguien escribe 2 * PESO_GRUPO(...).
  • CABE usa <= porque 1500 kg exactos caben (el grupo 3 lo comprueba: 20 · 75 = 1500 → «cabe»).
  • CABE(kg) ? "cabe" : "NO cabe" elige el texto con el operador ternario, y %-9s lo alinea.

Las cuentas, comprobadas:

GrupoPeso¿Cabe?Con un adulto más¿Cabe?
14 · 75 + 2 · 35 = 300 + 70 = 370Sí370 + 75 = 445Sí
215 · 75 + 6 · 35 = 1125 + 210 = 1335Sí1410Sí
320 · 75 + 0 = 1500Sí (justo)1575No
416 · 75 + 10 · 35 = 1200 + 350 = 1550No1625No

Solución del Nivel 2

#include <stdio.h>

#define PLANTA_MIN (-2)
#define PLANTA_MAX 8
#define NUM_PEDIDOS 7

#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define LIMITAR(x, lo, hi) MIN(MAX((x), (lo)), (hi))   /* primero el suelo, luego el techo */
#define ABS(x) ((x) < 0 ? -(x) : (x))

int main(void) {
    int pedidos[NUM_PEDIDOS] = {5, 12, -4, 0, 8, -2, 9};
    int actual = 0, corregidos = 0, recorrido = 0;

    for (int i = 0; i < NUM_PEDIDOS; i++) {
        int pedido = pedidos[i];          /* copia: nada de pedidos[i++] dentro de la macro */
        int planta = LIMITAR(pedido, PLANTA_MIN, PLANTA_MAX);
        if (planta != pedido)
            corregidos++;
        recorrido += ABS(planta - actual);   /* plantas que se mueve la cabina */
        printf("Pedido %3d -> va a %2d%s\n", pedido, planta,
               planta != pedido ? " (corregido)" : "");
        actual = planta;
    }
    printf("Corregidos: %d | Plantas recorridas: %d\n", corregidos, recorrido);
    return 0;
}

Por qué está hecho así:

  • LIMITAR(x, lo, hi) es MIN(MAX((x), (lo)), (hi)): MAX sube cualquier pedido por debajo de −2 hasta −2 y MIN baja cualquiera por encima de 8 hasta 8. Las macros se pueden anidar: el preprocesador vuelve a examinar el texto que sale de una sustitución.
  • PLANTA_MIN es (-2) entre paréntesis por la regla de siempre: es una expresión, no un número suelto.
  • int pedido = pedidos[i]; es la decisión clave. LIMITAR acaba repitiendo x varias veces al expandirse (tienes MAX dentro de MIN, y cada una repite sus parámetros), así que con LIMITAR(pedidos[i++], …) el índice avanzaría varias veces en una sola vuelta.
  • recorrido += ABS(planta - actual) suma la distancia en plantas, sin signo: bajar de 8 a −2 son 10 plantas, igual que subir.

La traza vuelta a vuelta:

Vueltapedidoplanta¿Corregido?ABS(planta - actual)recorrido
055No|5 − 0| = 55
1128Sí|8 − 5| = 38
2−4−2Sí|−2 − 8| = 1018
300No|0 − (−2)| = 220
488No|8 − 0| = 828
5−2−2No|−2 − 8| = 1038
698Sí|8 − (−2)| = 1048

Tres corregidos y 48 plantas, como en la salida.

Solución del Nivel 3

Primero, ascensor.h:

/* ascensor.h: constantes, macros y el struct del simulador */
#ifndef ASCENSOR_H
#define ASCENSOR_H

#define CARGA_MAXIMA 1500
#define PESO_MEDIO 75
#define PERSONAS_MAX (CARGA_MAXIMA / PESO_MEDIO)        /* 20 */

#define ABS(x) ((x) < 0 ? -(x) : (x))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define DIV_ARRIBA(a, b) (((a) + (b) - 1) / (b))        /* división redondeando hacia arriba */

struct Grupo {
    int origen;
    int destino;
    int personas;
};

#endif  /* ASCENSOR_H */

Luego, registro.h:

/* registro.h: trazas que solo existen al compilar con -DDEBUG */
#ifndef REGISTRO_H
#define REGISTRO_H

#include "ascensor.h"   /* se incluye otra vez desde el .c: la guarda lo evita */

#ifdef DEBUG
#define LOG(msg, valor) printf("  [%s:%d] %s = %d\n", __FILE__, __LINE__, msg, valor)
#else
#define LOG(msg, valor) ((void)0)
#endif

#endif  /* REGISTRO_H */

Y el programa:

#include <stdio.h>
#include "ascensor.h"
#include "registro.h"

#define NUM_GRUPOS 5

int main(void) {
    struct Grupo dia[NUM_GRUPOS] = {
        {0, 6, 8}, {0, 3, 25}, {4, -1, 12}, {7, 0, 41}, {-2, 5, 3}
    };
    int total_viajes = 0, total_plantas = 0, mas_lleno = 0;

    for (int i = 0; i < NUM_GRUPOS; i++) {
        struct Grupo g = dia[i];
        int viajes = DIV_ARRIBA(g.personas, PERSONAS_MAX);
        int tramo = ABS(g.destino - g.origen);
        /* ida, vuelta vacía a por más gente, ida...: 2 * viajes - 1 tramos */
        int plantas = tramo * (2 * viajes - 1);
        int en_cabina = MIN(g.personas, PERSONAS_MAX);   /* el viaje más lleno del grupo */
        LOG("viajes", viajes);
        LOG("plantas", plantas);

        printf("Grupo %d: %2d pers. %2d -> %2d | %d viaje(s), %2d plantas\n",
               i + 1, g.personas, g.origen, g.destino, viajes, plantas);
        total_viajes += viajes;
        total_plantas += plantas;
        if (en_cabina > mas_lleno)
            mas_lleno = en_cabina;
    }
    printf("Total: %d viajes, %d plantas, máximo %d personas (%d kg) a la vez\n",
           total_viajes, total_plantas, mas_lleno, mas_lleno * PESO_MEDIO);
    return 0;
}

Por qué está hecho así:

  • Las dos guardas (ASCENSOR_H y REGISTRO_H) son imprescindibles: simulador.c incluye ascensor.h directamente y otra vez a través de registro.h. Sin la guarda de ascensor.h, struct Grupo quedaría definido dos veces y no compilaría.
  • **DIV_ARRIBA(a, b)** es (((a) + (b) - 1) / (b)). Con la división entera normal, 41 / 20 = 2 y una persona se quedaría en la planta 7; sumando 19 antes, (41 + 19) / 20 = 60 / 20 = 3. Si ya era exacto, no cambia nada: (40 + 19) / 20 = 59 / 20 = 2.
  • **plantas = tramo * (2 * viajes - 1)**: con un viaje, el tramo una vez; con tres, ida, vuelta, ida, vuelta, ida = cinco veces.
  • **MIN(g.personas, PERSONAS_MAX)** es la gente que va en el viaje más lleno de ese grupo: si son 8, van los 8; si son 41, como mucho 20.
  • **LOG** sale de registro.h, así que el .c no sabe ni le importa si las trazas están encendidas. __LINE__ dice 20 y 21, las líneas de simulador.c donde se usa, aunque la macro esté escrita en otro archivo.

Las cuentas de cada grupo:

GrupoPersonasviajestramoplantasEn el viaje más lleno
18(8 + 19) / 20 = 1|6 − 0| = 66 · 1 = 68
225(25 + 19) / 20 = 233 · 3 = 920
3121|−1 − 4| = 5512
441377 · 5 = 3520
531|5 − (−2)| = 773

Total: 1 + 2 + 1 + 3 + 1 = 8 viajes y 6 + 9 + 5 + 35 + 7 = 62 plantas; el viaje más lleno lleva 20 personas, 20 · 75 = 1500 kg. El grupo 4 es el más interesante, y el dibujo lo enseña entero:

El grupo 4 del simulador Zigzag entre la planta 7 y la 0: tres bajadas con 20, 20 y 1 personas y dos subidas vacías; al lado, las cuentas: 3 viajes, 5 tramos de 7 plantas, 35 plantas. Grupo 4: 41 personas de la planta 7 a la 0 planta 7 planta 0 20 vacía 20 vacía 1 viajes = DIV_ARRIBA(41, 20) = (41 + 19) / 20 = 3 tramos = 2 * 3 – 1 = 5 tramo = ABS(0 – 7) = 7 plantas = 7 * 5 = 35 Tres idas con gente (20 + 20 + 1) y dos vueltas vacías: cinco tramos de siete plantas.
DIV_ARRIBA redondea hacia arriba: 41 / 20 daría 2 con la división entera, y una persona se quedaría en la planta 7.

Míralo paso a paso en Python Tutor

Python Tutor ejecuta el código línea a línea y dibuja las variables. Elige C y pega esta versión del fragmento 1, que guarda cada paso en una variable para que veas el fallo en directo:

#include <stdio.h>

#define CARGA_MAXIMA 1500
#define DEMASIADO_GRANDE(v) v >= CARGA_MAXIMA     /* sin paréntesis */

int main(void) {
    int carga = 300;
    int negado = !carga;                      /* lo que calcula de verdad el ! */
    int resultado = !DEMASIADO_GRANDE(carga); /* !carga >= 1500 */
    int bien = !(carga >= CARGA_MAXIMA);      /* lo que queríamos */
    printf("negado = %d, resultado = %d, bien = %d\n", negado, resultado, bien);
    return 0;
}

Su salida:

negado = 0, resultado = 0, bien = 1

Qué observar paso a paso:

  1. **carga vale 300** y es lo único que existe al empezar; ni CARGA_MAXIMA ni la macro aparecen como variables.
  2. **Al crearse negado**, vale 0: !300 es 0, porque cualquier número distinto de cero cuenta como «verdadero» y negarlo da «falso».
  3. **Al crearse resultado**, también vale 0. Es la macro sin paréntesis: !carga >= 1500 es negado >= 1500, es decir, 0 >= 1500.
  4. **Al crearse bien**, vale 1: con los paréntesis bien puestos, 300 >= 1500 es 0 y negarlo da 1. Esa es la diferencia entre «demasiado peso» y «puede subir».

Chuletario: el preprocesador en C

Lo esencial en 30 segundos: el preprocesador trabaja antes del compilador y solo con texto. #include pega archivos, #define sustituye símbolos (y macros con parámetros), #ifdef/#if dejan o quitan código, y gcc -D define símbolos desde fuera. En las macros, paréntesis en cada parámetro y alrededor de todo; nunca ; al final ni argumentos con efectos.

Directivas:

Quiero…CódigoResultado
Usar una biblioteca del sistema#include <stdio.h>Se pega stdio.h
Usar mi cabecera#include "ascensor.h"Se busca en mi carpeta
Una constante#define CARGA_MAXIMA 1500CARGA_MAXIMA → 1500
Un símbolo sin valor#define CON_PANTALLASolo existe
Una macro#define PESO(n) ((n) * 75)PESO(a + b) → ((a + b) * 75)
Borrar un símbolo#undef CON_PANTALLADeja de existir
Una macro de varias líneas\ al final de cada líneaUna sola definición

Compilación condicional:

Quiero…CódigoResultado
Trazas solo con -DDEBUG#ifdef DEBUG … #endifSin -DDEBUG, desaparecen
Valor por defecto cambiable#ifndef N / #define N 10 / #endif-DN=20 lo cambia
Elegir versión por número#if MODELO == 1 … #elif … #else … #endifSolo queda una rama
Apagar un bloque#if 0 … #endifNo llega al compilador
Impedir una compilación#error "mensaje"gcc para con ese mensaje
Definir desde la terminalgcc -DDEBUG / gcc -DMODELO=2Como un #define en la línea 1

Plantillas de los ejercicios:

PlantillaCódigoResultado
Peso de un grupo#define PESO_GRUPO(a, n) ((a) * 75 + (n) * 35)PESO_GRUPO(4, 2) → 370
Cabe o no#define CABE(kg) ((kg) <= CARGA_MAXIMA)CABE(1500) → 1
Mínimo y máximo#define MIN(a, b) ((a) < (b) ? (a) : (b))MIN(12, 8) → 8
Limitar a un rango#define LIMITAR(x, lo, hi) MIN(MAX((x), (lo)), (hi))LIMITAR(-4, -2, 8) → −2
Valor absoluto#define ABS(x) ((x) < 0 ? -(x) : (x))ABS(-10) → 10
División hacia arriba#define DIV_ARRIBA(a, b) (((a) + (b) - 1) / (b))DIV_ARRIBA(41, 20) → 3
Traza con ubicaciónprintf("[%s:%d] ...", __FILE__, __LINE__, ...)[simulador.c:20] ...
Guarda de un .h#ifndef ASCENSOR_H / #define ASCENSOR_H / … / #endifSe incluye una vez

Síntoma / causa probable:

SíntomaCausa probable
Una cuenta con una macro da un número raroFaltan paréntesis en un parámetro o alrededor de todo
Un !MACRO(x) nunca (o siempre) se cumpleFalta el paréntesis exterior de la macro
statement with no effect en una línea con un #define; al final del #define
Una variable avanza dos vecesHas pasado i++ a una macro que repite el parámetro
Las trazas salen aunque DEBUG vale 0Usas #ifdef DEBUG en vez de #if DEBUG
redefinition of 'struct …'Un .h sin guarda incluido dos veces
Muchos undeclared seguidosMira el primer error: un #error o un #include que falla

Antes de entregar, pregúntate:

  1. ¿Todas mis macros tienen paréntesis en cada parámetro y alrededor de la expresión?
  2. ¿Algún #define termina en ;?
  3. ¿Mis trazas desaparecen al compilar sin -DDEBUG (y no hay efectos dentro de ellas)?
  4. ¿Todos mis .h tienen su guarda #ifndef … #define … #endif?
Sigue aprendiendoTodos los artículos de C

¿Te ha servido este artículo? Así puedes apoyar SergioLearns →

Sigue aprendiendo

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *