Regla 0x500Ah: Protección obligatoria de parámetros en macros funcionales mediante paréntesis
Compilacion, preprocesador y seguridad (0x50XX)
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 consecuencia | Efecto concreto |
|---|---|
| Resultado incorrecto | CUADRADO(1 + 2) da 5 en vez de 9. |
| Bug silencioso | La macro compila y no advierte; el error depende del argumento. |
| Mantenibilidad | Cada invocación exige recordar la precedencia interna. |
| Evaluación repetida | Un 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¶
Argumentos con efecto colateral:
MAX(x++, y++)evalúa dos veces aunque estén los paréntesis; usá una función.Operador coma:
MULT((a, b), c)cambia de significado; evitá macros con expresiones que contengan comas.Macro como sentencia: para que
#define LOG(x) ...sea segura en unifsin llaves, envolvés endo { ... } while (0).
Cómo detectarla¶
| Herramienta | Comando | Señal |
|---|---|---|
gaff | gaff check archivo.c | Reporta 0x500Ah ante parámetros sin paréntesis en una macro. |
gcc | gcc -E archivo.c | Se ve la expansión textual del argumento sin agrupar. |
Checklist de autocontrol¶
¿Cada aparición de un parámetro está entre paréntesis?
¿La expresión completa está envuelta en paréntesis?
¿Evité invocar la macro con expresiones que tengan efectos colaterales?
¿Las macroconstantes sin parámetros están protegidas (ver 0x5013h: Protección obligatoria con paréntesis envolventes en expresiones de macroconstantes (#define))?
Reglas relacionadas¶
0x5013h: Protección obligatoria con paréntesis envolventes en expresiones de macroconstantes (#define) — la protección equivalente para macroconstantes.
0x500Dh: Prohibición de redefinir palabras clave o tipos primitivos de C con #define — tampoco se redefinen palabras clave con macros.
0x301Bh: Prohibición de casts de tipo innecesarios o redundantes — no castees de más, pero el paréntesis de macro nunca sobra.
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¶
El contador avanza de más:
itermina en un valor inesperado.El resultado numérico cambia sin ningún error ni advertencia.
El bug se reproduce solo cuando el argumento trae efectos colaterales, por eso el código parece “andar” con argumentos simples y falla en producción.
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=4La expresión CUADRADO(j++) expande ((j++) * (j++)) y modifica j dos
veces sin punto de secuencia: es comportamiento indefinido.
Checklist de verificación¶
¿Toda macro funcional tiene cada parámetro entre paréntesis?
¿La expansión repite algún parámetro más de una vez?
¿Se invoca la macro con argumentos que tienen
++,--o llamadas?¿Convierto la macro en
static inlinecuando la evaluación importa?¿Compilé con
-Wall -Wextray revisé-Wsequence-point?
Reglas relacionadas¶
0x500Ah: Protección obligatoria de parámetros en macros funcionales mediante paréntesis — paréntesis obligatorios en parámetros de macros funcionales.
0x5013h: Protección obligatoria con paréntesis envolventes en expresiones de macroconstantes (#define) — paréntesis envolventes en expresiones de macroconstantes.
0x100Ch: Espaciado obligatorio alrededor de operadores ternarios (‘? :’) — espaciado canónico alrededor del operador ternario.