Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Regla 0x500Ah: Protección obligatoria de parámetros en macros funcionales mediante paréntesis

Compilacion, preprocesador y seguridad (0x50XX)

Universidad Nacional de Río Negro

0x500Ah: Protección obligatoria de parámetros en macros funcionales mediante paréntesis

Enunciado normativo

DEBEN encerrarse entre paréntesis todos los parámetros que aparecen en el cuerpo de una macro funcional, y también la expresión completa resultante.

¿Por qué existe esta regla?

El problema

El preprocesador sustituye texto, no valores: antes de que exista cualquier concepto de precedencia, la macro CUADRADO(x) con cuerpo x * x se expande literalmente. Si se la invoca con 1 + 2, el compilador termina viendo 1 + 2 * 1 + 2, que por precedencia vale 5 y no 9. Lo mismo ocurre cuando la macro se usa embebida en una expresión mayor: sin paréntesis exteriores, MULT(a, b) + c cambia de agrupación.

El error es especialmente traicionero porque la macro «funciona» con argumentos simples y falla sólo con expresiones. Los paréntesis dobles son la única defensa textual contra la falta de tipos del preprocesador.

Consecuencias de violarla

Tipo de consecuenciaEfecto concreto
Resultado incorrectoCUADRADO(1 + 2) da 5 en vez de 9.
Bug silenciosoLa macro compila y no advierte; el error depende del argumento.
MantenibilidadCada invocación exige recordar la precedencia interna.
Evaluación repetidaUn argumento con efecto colateral se ejecuta dos veces.

Fundamento en el estándar y en la cátedra

ISO/IEC 9899:2011 §6.10.3 describe la sustitución de macros: los parámetros se reemplazan por la secuencia de tokens del argumento, sin evaluar ni agrupar. La cátedra exige la doble protección porque el código se lee asumiendo que una macro se comporta como una función, y el preprocesador no lo garantiza.

Alcance y excepciones

Aplica a todas las macros con parámetros (#define NOMBRE(args) ...). Las macroconstantes sin parámetros se rigen por 0x5013h: Protección obligatoria con paréntesis envolventes en expresiones de macroconstantes (#define). Excepción recomendada: para lógica con tipos, reemplazar la macro por una función static inline, que respeta precedencia y evalúa una sola vez.

Ejemplos exhaustivos

❌ Contraejemplo 1 — Sin paréntesis internos

#define CUADRADO(x) x * x

int main(void)
{
    return CUADRADO(1 + 2);
}

Por qué falla: se expande a 1 + 2 * 1 + 2, que vale 5. El programador lee (1 + 2) * (1 + 2) porque piensa en la macro como una función.

❌ Contraejemplo 2 — Paréntesis internos pero no exteriores

#define SUMA(a, b) (a) + (b)

int main(void)
{
    return SUMA(1, 2) * 3;
}

Por qué falla: se expande a (1) + (2) * 3, que por precedencia vale 7 y no 9. Los paréntesis internos protegen los parámetros, pero no a la macro en su conjunto cuando un operador la rodea.

✅ Ejemplo conforme 1 — Doble paréntesis

#define CUADRADO(x) ((x) * (x))
#define MULT(a, b) ((a) * (b))

int main(void)
{
    int a = CUADRADO(1 + 2);
    int b = -MULT(2, 3);
    return a + b;
}

Cada parámetro va entre paréntesis y la expresión completa también. Ahora CUADRADO(1 + 2) vale 9 y -MULT(2, 3) vale -6.

✅ Ejemplo conforme 2 — Función static inline

static inline int cuadrado(int x)
{
    return x * x;
}

static inline int maximo(int a, int b)
{
    if (a > b) {
        return a;
    }
    return b;
}

La función tiene tipos, respeta precedencia y evalúa cada argumento una sola vez. Es la alternativa preferida cuando no hace falta que la macro sea genérica sobre tipos.

⚠️ Casos límite

Cómo detectarla

HerramientaComandoSeñal
gaffgaff check archivo.cReporta 0x500Ah ante parámetros sin paréntesis en una macro.
gccgcc -E archivo.cSe ve la expansión textual del argumento sin agrupar.

Checklist de autocontrol

Reglas relacionadas

Antipatrón: Macro con argumentos evaluados múltiples veces

Síntoma en el código del estudiante

Macros funcionales cuya expansión aparece repetida en el cuerpo:

#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define CUADRADO(x) ((x) * (x))

Y se las invoca con expresiones que tienen efectos colaterales:

int m = MAX(i++, j);
int c = CUADRADO(x++);

El ++ se ejecuta tantas veces como aparece el parámetro en la expansión.

Diagnóstico

Mecanismo del defecto

El preprocesador hace sustitución textual antes de compilar (C11 §6.10.3): no hay paso de parámetros real ni copia de valores. Cada aparición de a, b o x en el cuerpo se reemplaza por el argumento completo. En MAX(i++, j), la expansión queda ((i++) > (j) ? (i++) : (j)), de modo que i se incrementa dos veces en la rama verdadera. Y si dos modificaciones de la misma variable caen sin punto de secuencia entre medio, el comportamiento es indefinido (C11 §6.5p2).

A esto se suma el problema de precedencia cuando faltan paréntesis: #define MULT(a, b) a * b expande MULT(2 + 3, 4) como 2 + 3 * 4, que da 14 en vez de 20.

Consecuencia observable

Fundamento en el estándar C11

Una macro no impone evaluación única ni ordenada: es texto. La forma correcta de encapsular una operación con evaluación controlada es una función, y para no perder el rendimiento se usa static inline (C11 §6.7.4), que el compilador puede incrustar. Los paréntesis protectores siguen siendo obligatorios en macros que no se puedan evitar, conforme a 0x500Ah: Protección obligatoria de parámetros en macros funcionales mediante paréntesis y 0x5013h: Protección obligatoria con paréntesis envolventes en expresiones de macroconstantes (#define).

Corrección idiomática

❌ Código con el antipatrón
#include <stdio.h>

#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define CUADRADO(x) ((x) * (x))

int main(void)
{
    int i = 7;
    int j = 3;
    int m = MAX(i++, j);
    int c = CUADRADO(j++);
    printf("%d %d %d %d\n", m, c, i, j);
    return 0;
}
✅ Código refactorizado
#include <stdio.h>

static inline int max_int(int a, int b)
{
    return a > b ? a : b;
}

static inline int cuadrado(int x)
{
    return x * x;
}

int main(void)
{
    int i = 7;
    int j = 3;
    int m = max_int(i++, j);
    int c = cuadrado(j++);
    printf("%d %d %d %d\n", m, c, i, j);
    return 0;
}

La función recibe valores: cada argumento se evalúa una sola vez antes de entrar al cuerpo, sin importar cuántas veces se use el parámetro.

Errores típicos al compilar o ejecutar

La mayoría de las veces no hay mensaje: el programa compila y da un resultado incorrecto. Con MAX(i++, j) e i > j, i se incrementa dos veces y la salida del código con el antipatrón difiere de la refactorizada:

antipatron: m=8 c=12 i=9 j=5
refactor  : m=7 c=9  i=8 j=4

La expresión CUADRADO(j++) expande ((j++) * (j++)) y modifica j dos veces sin punto de secuencia: es comportamiento indefinido.

Checklist de verificación

Reglas relacionadas