Regla 0x500Dh: Prohibición de redefinir palabras clave o tipos primitivos de C con #define
Compilacion, preprocesador y seguridad (0x50XX)
0x500Dh: Prohibición de redefinir palabras clave o tipos primitivos de C con #define¶
Enunciado normativo¶
NO DEBE usarse
#definepara redefinir palabras clave (if,while,int,return, ...), tipos primitivos ni macros de la biblioteca estándar (NULL,EOF,bool).
¿Por qué existe esta regla?¶
El problema¶
El preprocesador corre antes del análisis sintáctico, sin conocer la gramática
de C. Un #define int long no «crea un alias»: reemplaza la palabra int por
long en todo el archivo, incluidas las cabeceras del sistema. Redefinir NULL,
EOF o bool es peor todavía: esas macros ya existen y su redefinición está
reservada a la implementación (§7.1.3), con comportamiento indefinido y errores
imposibles de rastrear.
Consecuencias de violarla¶
| Tipo de consecuencia | Efecto concreto |
|---|---|
| Compilación | Errores en cascada en cabeceras del sistema que usan la palabra redefinida. |
| Comportamiento indefinido | Redefinir NULL, EOF o bool viola §7.1.3 (identificadores reservados). |
| Bug silencioso | #define true 1 funciona hasta que alguien necesita bool de verdad. |
Fundamento en el estándar y en la cátedra¶
ISO/IEC 9899:2011 §6.4.1 lista las palabras clave y §7.1.3 reserva los identificadores de la biblioteca, incluidos los macros estándar. Ninguna directiva del preprocesador cambia la gramática. La cátedra prohíbe la práctica porque rompe la portabilidad y hace inútil el análisis estático.
Alcance y excepciones¶
Aplica a palabras clave del lenguaje, tipos primitivos y macroconstantes
estándar. Excepciones legítimas: definir constantes propias con nombres
descriptivos (MAX_INTENTOS), crear typedef para dar nombre a un tipo, o
usar #define para activar una característica de una cabecera del sistema (por
ejemplo, _POSIX_C_SOURCE). Nada de eso reemplaza un token del lenguaje.
Ejemplos exhaustivos¶
❌ Contraejemplo 1 — Redefinir palabras clave¶
#define if while
#define int long
int main(void)
{
if (1) {
return 0;
}
}Por qué falla: #define if while convierte todo if en while; #define int long reescribe cada declaración de int, incluidas las de las cabeceras. El
programa deja de compilar de manera incomprensible.
❌ Contraejemplo 2 — Redefinir macros estándar¶
#define true 1
#define false 0
#define NULL 0
int main(void)
{
int *p = NULL;
return true;
}Por qué falla: NULL, true y false ya los proveen <stddef.h> y
<stdbool.h>; redefinirlos está reservado y es comportamiento indefinido.
Además NULL puede no ser 0 en todos los modelos de memoria.
✅ Ejemplo conforme 1 — Constante propia bien nombrada¶
#define MAX_INTENTOS 3
int main(void)
{
int intentos = 0;
while (intentos < MAX_INTENTOS) {
intentos++;
}
return intentos;
}La macro tiene un nombre de dominio que no colisiona con nada y su reemplazo es un literal, no una palabra del lenguaje.
✅ Ejemplo conforme 2 — Tipo con typedef y booleanos estándar¶
#include <stdbool.h>
typedef struct {
int x;
int y;
} punto_t;
bool punto_es_origen(punto_t p)
{
return p.x == 0 && p.y == 0;
}El alias de tipo se hace con typedef (con sufijo _t, ver 0x3004h: Utilizá typedef para definir tipos de estructuras con el sufijo _t) y
los booleanos provienen de <stdbool.h>, tal como exige 0x301Ah: Validador de uso idiomático de tipos booleanos estándar.
⚠️ Casos límite¶
#define NULL 0: está reservado; usá<stddef.h>.#define size_t ...:size_tes un tipo de la biblioteca, no una macro.Macros de configuración:
_POSIX_C_SOURCEo_GNU_SOURCEsí se definen antes de los#includepara habilitar funciones, no para reemplazar palabras del lenguaje.
Cómo detectarla¶
| Herramienta | Comando | Señal |
|---|---|---|
gaff | gaff check archivo.c | Reporta 0x500Dh si un #define reemplaza una palabra clave o un macro estándar. |
gcc / clang | gcc -std=c11 -Wall -Wextra -Werror -pedantic archivo.c | Errores de sintaxis en cadena al expandir la macro. |
Checklist de autocontrol¶
¿Algún
#definereemplaza una palabra clave, un tipo o un macro estándar?¿Usé
typedefen lugar de#definepara crear alias de tipos?¿Los booleanos vienen de
<stdbool.h>?¿Mis macros tienen nombres de dominio en
MAYUSCULAS_SNAKE_CASE?
Reglas relacionadas¶
0x301Ah: Validador de uso idiomático de tipos booleanos estándar — prohibición de redefiniciones manuales de booleanos.
0x0109h: Prohibición de identificadores que colisionen con palabras clave o tipos estándar — evitar identificadores que colisionen con palabras reservadas.
Antipatrón: Macro que ofusca sintaxis fundamental de C¶
Síntoma en el código del estudiante¶
Aparecen #define que sustituyen signos, palabras clave o bloques enteros de la
sintaxis de C:
#define BEGIN {
#define END }
#define FOREVER while (1)
#define ADIOS return 0El código resultante no se lee como C: el estudiante escribe un lenguaje inventado que sólo él conoce. Se reconoce a simple vista porque las llaves, los lazos o los retornos desaparecieron del cuerpo del programa.
Diagnóstico¶
Mecanismo del defecto¶
El preprocesador sustituye texto antes de que empiece el análisis sintáctico
(C11 §6.10). Al definir BEGIN como {, el programa fuente ya no contiene la
llave que el estándar espera en ese lugar: sólo la contiene después de la
expansión. El compilador y las herramientas de análisis —depuradores,
formateadores, resaltadores— trabajan sobre el texto expandido, así que el
alumno pierde la sintaxis que toda referencia de C documenta. Además, #define
de palabras clave o identificadores estándar entra en conflicto con la reserva
de §7.1.2 y con la prohibición de 0x500Dh: Prohibición de redefinir palabras clave o tipos primitivos de C con #define.
Consecuencia observable¶
Mientras la macro esté bien formada, el programa compila; el daño es de lectura
y de portabilidad. Pero cuando la macro envuelve un if/else o define llaves
en un punto inesperado, la expansión produce errores como
error: expected declaration or statement at end of input. El mensaje señala
la llave ausente, no la macro, y el estudiante no logra relacionar la causa.
Fundamento en el estándar C11¶
C11 §6.10.3 regula la sustitución de macros: es puramente textual, sin noción de bloques ni de tipos. §6.4.1 reserva las palabras clave y §7.1.2 los identificadores de la biblioteca; redefinirlos altera la semántica básica del lenguaje, algo que la cátedra prohibe en 0x500Dh: Prohibición de redefinir palabras clave o tipos primitivos de C con #define. La sintaxis de C debe permanecer visible para que el código sea legible por cualquier persona y por cualquier herramienta.
Corrección idiomática¶
❌ Código con el antipatrón¶
#define BEGIN {
#define END }
int main(void)
BEGIN
printf("hola\n");
ADIOS;
END✅ Código refactorizado¶
int main(void)
{
printf("hola\n");
return 0;
}Por qué mejora: las llaves y las palabras clave de C quedan a la vista; el
programa se lee con la misma sintaxis que cualquier manual. Si hace falta un
alias, se aplica a conceptos del dominio (#define CANTIDAD_MAXIMA 100), no a
la gramática del lenguaje, y con nombre en mayúsculas según 0x0107h: Las macros #define deben nombrarse en MAYUSCULAS_SNAKE_CASE.
Errores típicos al compilar o ejecutar¶
error: expected declaration or statement at end of input
error: expected '{' at end of input
warning: 'ADIOS' redefinedChecklist de verificación¶
Ninguna macro sustituye llaves, palabras clave ni operadores de C.
Las macros describen conceptos del dominio, no gramática.
No redefiní palabras clave ni identificadores estándar (0x500Dh: Prohibición de redefinir palabras clave o tipos primitivos de C con #define).
Todo
#defineestá enSCREAMING_SNAKE_CASE(0x0107h: Las macros #define deben nombrarse en MAYUSCULAS_SNAKE_CASE).Las expresiones de macro están protegidas con paréntesis (0x5013h: Protección obligatoria con paréntesis envolventes en expresiones de macroconstantes (#define)).
Reglas relacionadas¶
0x0107h: Las macros #define deben nombrarse en MAYUSCULAS_SNAKE_CASE — macros
#defineen mayúsculas sostenidas.0x500Dh: Prohibición de redefinir palabras clave o tipos primitivos de C con #define — prohibición de redefinir palabras clave con
#define.0x5013h: Protección obligatoria con paréntesis envolventes en expresiones de macroconstantes (#define) — paréntesis protectores en expresiones de macro.
0x0007h: Las llaves deben ubicarse en líneas independientes según el estilo Allman — llaves en estilo Allman, siempre visibles.
0x0001h: La claridad y prolijidad son de máxima importancia — la claridad es la meta que la ofuscación destruye.
Antipatrón: Redefinición de identificadores de funciones estándar de la biblioteca C¶
Síntoma en el código del estudiante¶
El estudiante define funciones con el mismo nombre que otras de la biblioteca estándar, creyendo que su versión es la única:
int abs(int x) { return x < 0 ? -x : x; }
int max(int a, int b) { return a > b ? a : b; }
void *memcpy(void *d, const void *s, size_t n) { ... }O usa como identificador propio un nombre reservado: free, time,
index, div, o un #define printf.
Diagnóstico¶
Mecanismo del defecto¶
El estándar reserva los identificadores con enlace externo que declaran las
cabeceras estándar para uso exclusivo de la implementación (C11 §7.1.3p1):
la libc puede reclamarlos como funciones, macros o atributos especiales.
Cuando el programa define abs y además incluye <stdlib.h>, el compilador
ve dos declaraciones incompatibles del mismo nombre externo y la unidad de
traducción no compila. Si no incluye la cabecera, la definición propia
queda con una firma que en el enlace compite contra el símbolo de la libc:
el enlazador informa “multiple definition” o, si la firma coincide, el
símbolo propio reemplaza al de la biblioteca de forma silenciosa y
cambia el comportamiento de todo el programa.
También están reservados, para uso futuro, los nombres que empiezan con
str o mem (C11 §7.31.13) y con is o to (C11 §7.31.2). Adoptarlos
expone a colisiones con funciones que el estándar agregue más adelante.
Consecuencia observable¶
error: conflicting types for 'abs'al incluir la cabecera correcta.multiple definition of 'abs'/undefined referenceen el enlace.Si el compilador no reclama, el programa llama a la función equivocada: un
minpropio puede pisar una macro de la plataforma y devolver resultados inesperados sin ningún aviso.
Fundamento en el estándar C11¶
La regla 0x500Bh exige incluir explícitamente la cabecera de cada función de
biblioteca. Esa inclusión es justamente la que revela la colisión: al
declarar abs en <stdlib.h> y en el fuente, el compilador detecta el
choque de firmas (C11 §6.2.7 y §6.5.2.2). La solución no es esconder la
cabecera, sino nombrar los identificadores propios con precisión, tal como
piden 0x0101h: Los identificadores deben ser descriptivos y 0x0105h: Los nombres de funciones deben usar snake_case estricto en minúsculas.
Corrección idiomática¶
❌ Código con el antipatrón¶
#include <stdlib.h>
int abs(int x)
{
return x < 0 ? -x : x;
}
int max(int a, int b)
{
return a > b ? a : b;
}✅ Código refactorizado¶
#include <stdlib.h>
static int valor_absoluto(int x)
{
return x < 0 ? -x : x;
}
static int maximo(int a, int b)
{
return a > b ? a : b;
}Los nombres propios describen el rol y no invaden el espacio reservado. El
calificador static restringe además el enlace al archivo, lo que evita
colisiones y aplica el ámbito mínimo de 0x2006h: Mantené el alcance de las variables al mínimo posible.
Errores típicos al compilar o ejecutar¶
aviso.c:6:5: error: conflicting types for 'abs'; have 'int(int)'
In file included from aviso.c:1:
/usr/include/stdlib.h:845:12: note: previous declaration of 'abs' with type 'int abs(int)'Y en el enlace, si se evitó la cabecera:
/usr/bin/ld: aviso.o: in function `abs':
aviso.c:(.text+0x0): multiple definition of `abs'; .../libc.a(abs.o): first defined here
collect2: error: ld returned 1 exit statusChecklist de verificación¶
¿Algún nombre definido coincide con una función estándar (
abs,max,memcpy,strlen)?¿Usé identificadores propios con prefijo o nombre de dominio claro?
¿Toda función interna es
static?¿Incluí la cabecera estándar antes de sospechar de una colisión?
¿Compilé con
-Wall -Wextray revisé advertencias de declaración?
Reglas relacionadas¶
0x500Bh: Inclusión obligatoria de cabeceras estándar para funciones de la biblioteca C — inclusión explícita de la cabecera de cada función estándar.
0x0101h: Los identificadores deben ser descriptivos — los identificadores deben ser descriptivos.
0x0105h: Los nombres de funciones deben usar snake_case estricto en minúsculas — los nombres de funciones en
snake_casepropio del módulo.