Regla 0x3009h: Documentá explícitamente los casos en que una función puede retornar NULL
Memoria, punteros y tipos (0x30XX)
0x3009h: Documentá explícitamente los casos en que una función puede retornar NULL¶
Enunciado normativo¶
Si una función que devuelve un puntero puede retornar
NULL, DEBE documentarse cuándo ocurre y qué significa cada caso en su documentación de retorno. Si la función nunca retornaNULL, esa garantía DEBE declararse también.
¿Por qué existe esta regla?¶
El problema¶
Un valor de retorno NULL es ambiguo: puede significar “no se encontró”, “no hay
memoria”, “argumento inválido” o “fin de recorrido”. Si la documentación no lo
aclara, el llamador debe adivinarlo leyendo el cuerpo y cada persona decidirá
distinto. La documentación es el único lugar donde se asocia cada NULL con su
condición.
Consecuencias de violarla¶
| Tipo de consecuencia | Efecto concreto |
|---|---|
| Comportamiento indefinido | El llamador desreferencia un NULL que creía imposible. |
| Bug silencioso | “No encontrado” y “sin memoria” se tratan igual y el error se oculta. |
| Robustez | Es imposible manejar el caso de fallo porque no se sabe cuándo ocurre. |
Fundamento en el estándar y en la cátedra¶
C11 §7.22.3 sienta el precedente: las funciones que reservan memoria documentan que devuelven un puntero nulo si fallan. La cátedra extiende esa disciplina a toda función que retorne punteros, dentro del marco de documentación de 0x2003h: Todas las funciones deben incluir documentación completa y estructurada. La regla es hermana de 0x3006h: Documentá la propiedad de los recursos al utilizar punteros: una describe la propiedad, la otra, la validez.
Alcance y excepciones¶
Aplica a funciones y a funciones de interfaz de TAD que devuelvan T *. No
aplica a funciones que retornan punteros garantizados no nulos, aunque también
deben decirlo (@return nunca es NULL). Tampoco a funciones que no devuelven
punteros. En funciones static de uso interno, la documentación puede ser más
breve, pero la condición de retorno nulo no se omite.
Ejemplos exhaustivos¶
❌ Contraejemplo 1 — Búsqueda sin documentación de retorno¶
nodo_t *buscar(nodo_t *cabeza, int id);Por qué falla: el llamador no sabe si NULL significa “no está en la lista” (un
resultado normal) o “la lista está corrupta” (un error). Distintas personas
escribirán distinto código de manejo, y probablemente nadie contemple el caso
nulo.
❌ Contraejemplo 2 — Documentación que promete de más¶
/**
* @return Puntero al nodo solicitado.
*/
nodo_t *buscar(nodo_t *cabeza, int id)
{
while (cabeza != NULL)
{
if (cabeza->id == id)
{
return cabeza;
}
cabeza = cabeza->sig;
}
return NULL;
}Por qué falla: la documentación afirma que siempre devuelve un puntero válido,
pero la implementación retorna NULL cuando no encuentra el nodo. El contrato
escrito contradice al real y el llamador que confíe en él desreferenciará un
nulo.
✅ Ejemplo conforme 1 — Documentar la condición de NULL¶
/**
* @brief Busca un nodo por identificador.
* @param cabeza Inicio de la lista; puede ser NULL (lista vacía).
* @param id Identificador buscado.
* @return Puntero al nodo con ese id, o NULL si no existe o si la
* lista está vacía. El llamador no es dueño del nodo devuelto.
*/
nodo_t *buscar(nodo_t *cabeza, int id);La documentación responde las tres preguntas: cuándo NULL, qué significa y
quién es dueño. El llamador puede decidir sin leer el cuerpo, y la propiedad
queda amarrada a 0x3006h: Documentá la propiedad de los recursos al utilizar punteros.
✅ Ejemplo conforme 2 — Distinguir causas con un parámetro de salida¶
/**
* @brief Busca un nodo y distingue el motivo del fallo.
* @param cabeza Lista de sólo lectura.
* @param id Identificador buscado.
* @param encontrado Salida: true si el nodo existe, false si no.
* @return Puntero al nodo, o NULL si no existe o si falla la búsqueda.
* @pre encontrado != NULL.
*/
nodo_t *buscar_detallado(const nodo_t *cabeza, int id, bool *encontrado);Cuando “no encontrado” y “error” deben diferenciarse, se usa una salida
explícita. La documentación declara esa salida y ambas condiciones, en lugar de
sobrecargar el significado de NULL.
⚠️ Casos límite¶
Errores diferenciados: si hay varios motivos de
NULL, considerá unenumde error como salida adicional y documentalo.Contrato opuesto: una función que nunca devuelve
NULLconviene documentarla así; el llamador puede omitir el chequeo (y 0x3001h: Siempre verificá la asignación exitosa de memoria dinámica sigue aplicando sólo a la reserva, no al retorno).
Cómo detectarla¶
| Herramienta | Comando | Señal |
|---|---|---|
gaff | gaff check archivo.c | Retorno NULL sin documentación asociada en el bloque de la función. |
gcc / clang | gcc -Wall -Wextra -std=c11 -fanalyzer ... | possible null pointer dereference en el llamador. |
| Revisión manual | — | Llamador que desreferencia el retorno sin consultar la documentación. |
Checklist de autocontrol¶
¿Documenté cuándo devuelve
NULLmi función?¿Expliqué qué significa cada caso de
NULL?¿Dije quién es dueño del puntero devuelto?
¿Si nunca devuelve
NULL, lo declaré explícitamente?¿Si hay varios motivos, distinguí “no encontrado” de “error”?
Reglas relacionadas¶
0x3006h: Documentá la propiedad de los recursos al utilizar punteros — la propiedad del recurso se documenta junto con el retorno.
0x300Eh: Documentá explícitamente el comportamiento de las funciones al manejar punteros nulos como argumentos — el comportamiento ante argumentos nulos completa el contrato.
0x2003h: Todas las funciones deben incluir documentación completa y estructurada — la documentación estructurada aloja estas anotaciones.
0x3001h: Siempre verificá la asignación exitosa de memoria dinámica — todo retorno
NULLde reserva debe verificarse en el llamador.