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 0x500Dh: Prohibición de redefinir palabras clave o tipos primitivos de C con #define

Compilacion, preprocesador y seguridad (0x50XX)

Universidad Nacional de Río Negro

0x500Dh: Prohibición de redefinir palabras clave o tipos primitivos de C con #define

Enunciado normativo

NO DEBE usarse #define para 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 consecuenciaEfecto concreto
CompilaciónErrores en cascada en cabeceras del sistema que usan la palabra redefinida.
Comportamiento indefinidoRedefinir 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

Cómo detectarla

HerramientaComandoSeñal
gaffgaff check archivo.cReporta 0x500Dh si un #define reemplaza una palabra clave o un macro estándar.
gcc / clanggcc -std=c11 -Wall -Wextra -Werror -pedantic archivo.cErrores de sintaxis en cadena al expandir la macro.

Checklist de autocontrol

Reglas relacionadas

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 0

El 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' redefined

Checklist de verificación

Reglas relacionadas

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

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 status

Checklist de verificación

Reglas relacionadas