Regla 0x3008h: Los punteros nulos deben ser inicializados y comparados con NULL, no con 0
Memoria, punteros y tipos (0x30XX)
0x3008h: Los punteros nulos deben ser inicializados y comparados con NULL, no con 0¶
Enunciado normativo¶
Los punteros nulos DEBEN inicializarse y compararse mediante la macro
NULL, y NO DEBEN compararse contra el literal entero0ni contra el carácter'\0'.
¿Por qué existe esta regla?¶
El problema¶
0 y NULL son compatibles para el compilador: NULL se expande, según la
cabecera, a 0, 0L o ((void *)0). Sin embargo, la semántica para el lector
es distinta. Un 0 en una comparación puede ser un entero legítimo, un índice o
un contador; NULL sólo puede ser un puntero. Escribir NULL documenta la
intención sin ambigüedad.
El caso de '\0' es más grave: su valor es 0, de modo que
if (ptr == '\0') compila y compara el puntero contra el entero cero. Pero
'\0' describe el terminador de una cadena, no un puntero nulo; la mezcla de
tipos confunde cadenas con punteros y es una fuente de errores didácticos.
Consecuencias de violarla¶
| Tipo de consecuencia | Efecto concreto |
|---|---|
| Ambigüedad | El lector no distingue si 0 es un entero o un puntero nulo. |
| Confusión de tipos | '\0' sugiere una cadena donde hay un puntero. |
| Consistencia | Conviven == 0, == NULL y == '\0' para la misma idea. |
| Portabilidad de estilo | Otros entornos esperan NULL/nullptr; 0 no comunica. |
Fundamento en el estándar y en la cátedra¶
C11 §7.19 define NULL en <stddef.h> como una constante de puntero nulo. Una
constante de puntero nulo es un entero constante con valor 0 o una
expresión con el cast a void *; comparar contra ella es válido pero no
expresivo. La cátedra adopta NULL por coherencia semántica y lo distingue de
'\0' y de 0 en 0x1005h: Reemplazá las condiciones ambiguas basadas en la ‘veracidad’ (truthiness) del tipo de dato, que exige comparaciones explícitas según el
tipo.
Alcance y excepciones¶
Aplica a la inicialización, asignación, retorno y comparación de punteros.
No aplica a enteros: int x = 0; es correcto y no debe cambiarse por NULL.
Tampoco prohíbe '\0' para comparar caracteres: if (c == '\0') es la forma
correcta para el terminador de una cadena. La regla es específica de punteros.
Ejemplos exhaustivos¶
❌ Contraejemplo 1 — Puntero inicializado con 0¶
int *ptr = 0;
if (ptr == 0)
{
printf("Sin datos\n");
}Por qué falla: el código es válido, pero 0 no dice que ptr sea un puntero
nulo. Un lector apurado puede interpretarlo como un índice o como “posición
cero”. NULL elimina esa duda y coincide con la convención del catálogo.
❌ Contraejemplo 2 — Comparación con el terminador de cadena¶
char *nombre = buscar_nombre(id);
if (nombre == '\0')
{
printf("No encontrado\n");
}Por qué falla: '\0' es un int con valor 0, así que la comparación se
interpreta como nombre == 0. Compila, pero mezcla el espacio de las cadenas
con el de los punteros: para el lector, nombre parece un char. La forma
correcta es nombre == NULL.
✅ Ejemplo conforme 1 — Inicialización y comparación con NULL¶
int *ptr = NULL;
if (ptr == NULL)
{
printf("Sin datos\n");
}NULL deja claro que ptr es un puntero y que la condición pregunta por su
validez. El mismo criterio se aplica al retorno de una búsqueda y a los
parámetros.
✅ Ejemplo conforme 2 — Convivencia de NULL y '\0' en su tipo¶
const char *texto = obtener_texto();
if (texto != NULL)
{
size_t i = 0;
while (texto[i] != '\0')
{
i++;
}
}El puntero se compara con NULL; el carácter, con '\0'. Cada valor se compara
contra el centinela de su propio tipo, en línea con 0x1005h: Reemplazá las condiciones ambiguas basadas en la ‘veracidad’ (truthiness) del tipo de dato.
⚠️ Casos límite¶
NULLpuede expandirse a0: el preprocesador no distingue; la ventaja es para el lector, no para el compilador.Estilo Yoda:
if (NULL == ptr)está permitido por la cátedra, pero 0x100Bh: No utilices comparaciones en estilo Yoda (‘CONST == variable’) prefiereptr == NULLpor lectura natural.C23: introduce
nullptr; la cátedra sigue conNULLen C11.free(NULL): comparar antes de liberar es innecesario; ver 0x3008h: Los punteros nulos deben ser inicializados y comparados con NULL, no con 0.
Cómo detectarla¶
| Herramienta | Comando | Señal |
|---|---|---|
gaff | gaff check archivo.c | Puntero comparado o inicializado con 0 o '\0'. |
gcc / clang | gcc -Wall -Wextra -std=c11 ... | comparison of pointer with integer zero según el contexto. |
| Revisión manual | — | Uso de NULL mezclado con 0/'\0' para punteros. |
Checklist de autocontrol¶
¿Inicialicé los punteros con
NULL?¿Comparo punteros contra
NULL, no contra0?¿Uso
'\0'sólo para caracteres?¿Mantuve
0para enteros y contadores?¿Evité comparar antes de
free, que ya toleraNULL?
Reglas relacionadas¶
0x1005h: Reemplazá las condiciones ambiguas basadas en la ‘veracidad’ (truthiness) del tipo de dato — comparaciones explícitas según el tipo de dato.
0x3019h: Prohibición de comparar punteros contra constantes numéricas distintas de NULL o cero — prohibición de comparar punteros contra enteros distintos de cero.
0x3002h: Liberá siempre la memoria dinámica y asigná NULL al puntero para mitigar punteros colgantes — la anulación con
NULLpresupone este uso consistente.0x100Bh: No utilices comparaciones en estilo Yoda (‘CONST == variable’) — estilo Yoda y su relación con las comparaciones.
Antipatrón: Chequeo innecesario antes de free()¶
Síntoma en el código del estudiante¶
Cada liberación se envuelve en una condición que “protege” a free de recibir
un puntero nulo:
if (ptr != NULL) {
free(ptr);
}El chequeo se repite en cada free, como si liberar un nulo fuera peligroso.
Diagnóstico¶
Mecanismo del defecto¶
El defecto no es de memoria sino de ruido y falsa premisa. La creencia de
fondo es “free(NULL) explota”. El estándar dice lo contrario: si el argumento
es un puntero nulo, free no realiza ninguna acción. Por lo tanto, el if no
cambia el comportamiento del programa: agrega una rama que ya estaba cubierta.
Ese ruido tiene un costo real: oculta la verdadera pregunta de propiedad (¿este
bloque sigue vivo?), sugiere que el autor no confía en el contrato de free y
multiplica la superficie de revisión.
Consecuencia observable¶
Código más largo y con más anidación sin beneficio semántico.
Duplicación del patrón en todo el proyecto.
A veces el
ifse combina confreepara “anular” y el estudiante cree que elifes lo que evita el doblefree, cuando en realidad la clave esptr = NULL.
Fundamento en el estándar C11¶
C11 §7.22.3.3p2: “If
ptris a null pointer, no action occurs.” La operación es un no-op definido.C11 §7.22.3.3p2 también fija que, tras
free, el valor del puntero queda indeterminado; por eso la cátedra exige anularlo (regla 0x3002h).La comparación debe escribirse
ptr != NULLy noptr != 0para mantener la semántica de punteros (regla 0x3008h).
Corrección idiomática¶
❌ Código con el antipatrón¶
if (ptr != NULL) {
free(ptr);
}✅ Código refactorizado¶
free(ptr);
ptr = NULL;La línea útil es la asignación a NULL; el if desaparece porque no protegía
nada.
✅ Ejemplo conforme 2 — liberación de una lista¶
while (cabeza != NULL) {
nodo_t *siguiente = cabeza->sig;
free(cabeza);
cabeza = siguiente;
}Acá sí hay una pregunta legítima (cabeza != NULL) porque se está recorriendo
la lista, no porque se tema liberar nulo.
⚠️ Casos límite¶
El
ifsí tiene sentido si además hay que hacer otra cosa por rama, p. ej. contar recursos liberados; en ese caso no es un chequeo “parafree”.freesobre un puntero no inicializado (basura de pila) es UB; el problema no es elif, es no inicializar (regla 0x7001h y0x3001h).Confundir
free(NULL)seguro confree(ptr_dangling)seguro: lo segundo es doble liberación.
Errores típicos al compilar o ejecutar¶
# El chequeo no protege contra el error real:
$ ./programa
free(): double free detected in tcache 2
Aborted (core dumped)
# Este patrón no produce advertencia; es estilo, no diagnóstico de compilador.Checklist de verificación¶
¿Eliminé los
if (ptr != NULL)que solo envuelven unfree?¿Después de cada
freeasignéNULLal puntero?¿Comparo punteros contra
NULL, nunca contra0?¿El
ifque quedó pregunta algo distinto de “¿es nulo antes de liberar?”?
Reglas relacionadas¶
0x3002h: Liberá siempre la memoria dinámica y asigná NULL al puntero para mitigar punteros colgantes — liberar y anular el puntero.
0x3008h: Los punteros nulos deben ser inicializados y comparados con NULL, no con 0 — usar
NULLen comparaciones de punteros.0x7001h: Siempre debés inicializar las variables a un valor conocido — inicializar variables antes de usarlas.
0x3008h: Los punteros nulos deben ser inicializados y comparados con NULL, no con 0 — otra comparación mal formada.
Antipatrón: Comparación sintáctica errónea de puntero con carácter nulo ‘\0’¶
Síntoma en el código del estudiante¶
Para detectar el fin de una cadena se compara el puntero con el carácter nulo:
if (str == '\0') {
/* ... */
}El estudiante quiso preguntar “¿la cadena está vacía?” o “¿llegué al final?”, pero comparó la dirección con el valor cero.
Diagnóstico¶
Mecanismo del defecto¶
'\0' es una constante de tipo int con valor 0. Al comparar un puntero
contra ella, C convierte el 0 en la constante de puntero nulo, de modo que
str == '\0' es equivalente a str == NULL. La expresión compila y no advierte
nada, pero no mide el contenido de la cadena: solo pregunta si el puntero es
nulo.
Para saber si el primer carácter es el terminador hay que desreferenciar:
*str == '\0' o, con azúcar, str[0] == '\0'. Recién ahí se lee el objeto
apuntado.
Una sutileza: '\0' es entero, no carácter. La comparación idiomática de
caracteres es contra '\0', pero la de punteros es contra NULL; mezclar
dominios es el error de fondo.
Consecuencia observable¶
La condición nunca es verdadera para un puntero válido, así que el caso “cadena vacía” no se detecta.
Posible desreferencia posterior de un puntero que se creía no nulo.
Silencio total del compilador: es una comparación legal y peligrosa.
Fundamento en el estándar C11¶
C11 §6.3.2.3p3: la constante entera
0se convierte en puntero nulo al compararla con un puntero.C11 §6.5.3.2: para examinar el carácter apuntado hay que aplicar
*.C11 §7.24.1: el terminador de una cadena es el carácter nulo
'\0'.La regla 0x3008h separa los dos dominios:
NULLpara punteros,'\0'para caracteres.
Corrección idiomática¶
❌ Código con el antipatrón¶
if (str == '\0') {
return 0;
}✅ Código refactorizado — primer carácter¶
if (str == NULL) {
return -1;
}
if (*str == '\0') {
return 0;
}✅ Recorrido de cadena con la distinción correcta¶
while (*str != '\0') {
procesar(*str);
str++;
}Acá *str es un char y se compara contra '\0', que es su contraparte
natural.
⚠️ Casos límite¶
if (str[0] == '\0')es equivalente y también correcto.if (str == NULL)sigue siendo necesario antes de desreferenciar.No confundir
'\0'(carácter nulo) con"0"(cadena que contiene el dígito cero) ni con'0'(carácter dígito).En comparaciones de punteros siempre
NULL; la cátedra prohíbe!= 0para punteros.
Errores típicos al compilar o ejecutar¶
$ gcc -Wall -Wextra -std=c11 programa.c
# sin advertencia: la comparación puntero/0 es legal
$ ./programa
# la rama de "cadena vacía" nunca se ejecuta y el flujo continúa
Segmentation fault (core dumped)Checklist de verificación¶
¿Comparo caracteres con
'\0'y punteros conNULL?¿Desreferencié (
*strostr[0]) antes de comparar el contenido?¿Verifiqué
str != NULLantes de desreferenciarlo?¿Ninguna comparación de puntero usa
0o'\0'en lugar deNULL?
Reglas relacionadas¶
0x3008h: Los punteros nulos deben ser inicializados y comparados con NULL, no con 0 —
NULLpara punteros, no0.0x1005h: Reemplazá las condiciones ambiguas basadas en la ‘veracidad’ (truthiness) del tipo de dato — comparaciones explícitas, sin truthiness.
0x5004h: Todas las operaciones con cadenas deben ser seguras — operaciones seguras con cadenas.
0x3008h: Los punteros nulos deben ser inicializados y comparados con NULL, no con 0 — mismo campo de comparaciones de punteros.