Regla 0x300Eh: Documentá explícitamente el comportamiento de las funciones al manejar punteros nulos como argumentos
Memoria, punteros y tipos (0x30XX)
0x300Eh: Documentá explícitamente el comportamiento de las funciones al manejar punteros nulos como argumentos¶
Enunciado normativo¶
Si una función acepta punteros
NULLcomo argumento, DEBE documentar el comportamiento que adopta en ese caso. Si NO los acepta, DEBE declararlo como precondición explícita. La ambigüedad no está permitida.
¿Por qué existe esta regla?¶
El problema¶
Recibir NULL tiene dos respuestas legítimas: tratarlo como valor válido con un
significado definido (“lista vacía”) o rechazarlo como precondición incumplida.
Lo que no es correcto es no decidir. Sin documentación, algunos llamadores
provocarán un SIGSEGV y otros agregarán chequeos que la función ya podría
garantizar.
Consecuencias de violarla¶
| Tipo de consecuencia | Efecto concreto |
|---|---|
| Comportamiento indefinido | La función desreferencia NULL y falla en ejecución. |
| Contratos duplicados | Cada llamador repite la misma validación por desconfianza. |
| Bug silencioso | Un NULL con significado especial se interpreta como error, o al revés. |
Fundamento en el estándar y en la cátedra¶
C11 no impone un comportamiento ante punteros nulos; cada función lo define. La cátedra exige que esa decisión sea explícita en la documentación de 0x2003h: Todas las funciones deben incluir documentación completa y estructurada, junto con los retornos de 0x3009h: Documentá explícitamente los casos en que una función puede retornar NULL.
Alcance y excepciones¶
Aplica a todo parámetro de tipo puntero. No aplica a parámetros que no son
punteros (un size_t no admite NULL). La decisión se escribe como precondición
(@pre ptr != NULL) o como nota de comportamiento.
Ejemplos exhaustivos¶
❌ Contraejemplo 1 — Función que no decide ni documenta¶
void procesar(nodo_t *nodo)
{
nodo->dato = 0;
nodo->sig = NULL;
}Por qué falla: si el llamador pasa NULL, la primera línea desreferencia la
dirección cero y el programa falla. La función no dice si acepta NULL ni
valida su entrada. El contrato es una incógnita que sólo se descubre al ejecutar.
❌ Contraejemplo 2 — Validación defensiva que oculta el contrato¶
void imprimir(const lista_t *lista)
{
if (lista == NULL)
{
return;
}
while (lista != NULL)
{
printf("%d\n", lista->dato);
lista = lista->sig;
}
}Por qué falla: la función tolera NULL (imprime nada), pero no lo declara. El
llamador que lea la firma no lo sabe y probablemente agregue su propio if,
duplicando la validación. La tolerancia debe ser parte del contrato escrito.
✅ Ejemplo conforme 1 — Precondición explícita¶
/**
* @brief Inicializa un nodo ya reservado.
* @param nodo Nodo a inicializar.
* @pre nodo != NULL (el llamador garantiza la reserva).
* @post nodo->dato es 0 y nodo->sig es NULL.
*/
void nodo_inicializar(nodo_t *nodo)
{
nodo->dato = 0;
nodo->sig = NULL;
}La precondición libera a la función de validar y deja claro al llamador que debe pasar un puntero válido. Si la precondición se rompe, es un error del llamador.
✅ Ejemplo conforme 2 — Tolerancia documentada¶
/**
* @brief Imprime los elementos de una lista.
* @param lista Lista a recorrer. Si es NULL, no imprime nada
* (se interpreta como lista vacía).
* @pre Ninguna.
*/
void lista_imprimir(const lista_t *lista)
{
while (lista != NULL)
{
printf("%d\n", lista->dato);
lista = lista->sig;
}
}La función decide tratar NULL como lista vacía y lo documenta. El llamador no
necesita un chequeo previo ni teme un fallo. La tolerancia documentada
complementa la propiedad de 0x3006h: Documentá la propiedad de los recursos al utilizar punteros.
⚠️ Casos límite¶
NULLcon significado: enfree(NULL)es un no-op; documentar ese comportamiento es correcto.Funciones que sí fallan: si
NULLes un error recuperable, devolvé un valor de error y documentalo conforme a 0x2007h: Los valores de retorno numéricos deben definirse como constantes de preprocesador o enums.Doble puntero: documentá también si
*ptrpuede serNULL, no sóloptr.
Cómo detectarla¶
| Herramienta | Comando | Señal |
|---|---|---|
gaff | gaff check archivo.c | Parámetro de puntero sin @pre ni nota de tolerancia a NULL. |
gcc / clang | gcc -Wall -Wextra -std=c11 -fanalyzer ... | possible null pointer dereference en el cuerpo. |
| Revisión manual | — | Llamadores que validan lo mismo que la función. |
Checklist de autocontrol¶
¿Decidí si mi función acepta
NULL?¿Lo documenté como precondición o como comportamiento?
¿Evité validar dos veces lo mismo en el llamador y en la función?
¿Documenté también el estado de
*ptren punteros dobles?
Reglas relacionadas¶
0x3009h: Documentá explícitamente los casos en que una función puede retornar NULL — la otra mitad del contrato: cuándo retorna
NULL.0x3006h: Documentá la propiedad de los recursos al utilizar punteros — la propiedad del recurso se documenta junto a la tolerancia.
0x2003h: Todas las funciones deben incluir documentación completa y estructurada — la documentación estructurada aloja precondiciones y postcondiciones.
0x2001h: Las funciones deben usar cláusulas de guarda y retornos anticipados para reducir la anidación profunda — las cláusulas de guarda implementan el rechazo de nulos.