Regla 0x3006h: Documentá la propiedad de los recursos al utilizar punteros
Memoria, punteros y tipos (0x30XX)
0x3006h: Documentá la propiedad de los recursos al utilizar punteros¶
Enunciado normativo¶
Cuando una función recibe o devuelve un puntero a un recurso, su documentación DEBE indicar quién es el dueño del recurso y, por lo tanto, quién debe liberarlo. Si la función transfiere la propiedad, DEBE declararlo; si sólo la presta, también.
¿Por qué existe esta regla?¶
El problema¶
En C no existe un recolector de basura ni un sistema de tipos que codifique la
propiedad de la memoria. El compilador ve un char * y no sabe si apunta a una
cadena literal (no liberable), a memoria del llamador (prestada) o a un bloque
recién reservado (transferido). Esa información sólo vive en la
documentación.
Sin ella, dos errores simétricos son igual de probables: fugar memoria porque nadie libera, o liberar dos veces porque ambos lados creyeron ser dueños. El contrato debe decir explícitamente qué espera la función del llamador y qué entrega de vuelta.
Consecuencias de violarla¶
| Tipo de consecuencia | Efecto concreto |
|---|---|
| Fuga de memoria | Nadie libera porque cada parte asumió que la otra lo haría. |
| Doble liberación | Ambos lados liberan el mismo bloque por no saber quién es dueño. |
| Uso de memoria prestada | El llamador libera datos que pertenecen a otro módulo. |
| Mantenibilidad | El código cambia cuando alguien descubre el contrato real por ensayo y error. |
Fundamento en el estándar y en la cátedra¶
C11 no define la propiedad como concepto del lenguaje; por eso la cátedra la convierte en requisito documental de toda función con punteros, en el marco de 0x2003h: Todas las funciones deben incluir documentación completa y estructurada (documentación estructurada). Es la contraparte explícita de 0x3005h: Minimizá el uso de múltiples niveles de indirección (punteros a punteros): cuanto más niveles de indirección hay, más importante es decir quién responde por cada uno.
Alcance y excepciones¶
Aplica a toda función que reciba o devuelva T * o T ** hacia memoria
dinámica, archivos, sockets u otros recursos. No aplica a punteros que
apuntan a objetos del propio llamador sin transferencia de vida (por ejemplo,
size_t * para un contador local), aunque documentar la intención nunca
estorba. Las funciones static internas pueden tener documentación más breve si
el contrato es evidente en el módulo, pero la anotación de propiedad no se omite.
Ejemplos exhaustivos¶
❌ Contraejemplo 1 — Constructor sin contrato de propiedad¶
char *crear_cadena(const char *origen);Por qué falla: el llamador no sabe si debe liberar el retorno con free, si la
cadena es estática, ni si puede fallar. Distintas personas escribirán distinto
código de limpieza y aparecerán fugas o dobles liberaciones. La firma sola no
alcanza: hace falta documentar.
❌ Contraejemplo 2 — Función que libera memoria del llamador¶
void procesar(nodo_t *nodo)
{
usar(nodo->dato);
free(nodo);
}Por qué falla: procesar recibe un puntero prestado y lo libera sin avisar. El
llamador conserva su puntero y lo vuelve a liberar o lo usa: use-after-free y
double free. El nombre no sugiere destrucción; el contrato está oculto en el
cuerpo.
✅ Ejemplo conforme 1 — Documentar transferencia de propiedad¶
/**
* @brief Reserva una copia de la cadena.
* @param origen Cadena de sólo lectura; no debe ser NULL.
* @return Puntero reservado con malloc. El LLAMADOR es dueño y
* debe liberarlo con free(); devuelve NULL si no hay memoria.
*/
char *cadena_copiar(const char *origen);El bloque @return fija las tres preguntas: quién libera, con qué función, y
qué significa NULL. El cliente no necesita leer la implementación.
✅ Ejemplo conforme 2 — Documentar préstamo sin transferencia¶
/**
* @brief Recorre la lista e imprime sus elementos.
* @param lista Lista de sólo lectura; el llamador conserva su propiedad
* y no debe liberarla por esta llamada.
* @pre lista != NULL.
* @post No se modifica el estado de la lista.
*/
void lista_imprimir(const lista_t *lista);El @param aclara que la función no libera ni retiene el recurso, y el
const de 0x3007h: Los argumentos de tipo puntero deben ser const siempre que la función no los modifique lo refuerza a nivel de tipos. Documentación y firma
dicen lo mismo.
⚠️ Casos límite¶
Propiedad compartida: si el recurso se comparte (por ejemplo, un buffer global del módulo), documentalo y liberalo una sola vez, al final del programa.
Transferencia parcial: una función puede devolver un puntero al interior de un bloque que el llamador no debe liberar; decilo con claridad.
NULLcomo “sin recurso”: si el retorno puede serNULL, combinalo con 0x3009h: Documentá explícitamente los casos en que una función puede retornar NULL para no dejar dudas.
Cómo detectarla¶
| Herramienta | Comando | Señal |
|---|---|---|
gaff | gaff check archivo.c | Función con T * sin bloque de documentación de propiedad. |
gcc / clang | gcc -Wall -Wextra -std=c11 ... | No evalúa documentación. |
| Revisión manual | — | Llamadores con free incierto o ausente sobre el retorno. |
Checklist de autocontrol¶
¿Dije quién libera el recurso que devuelvo?
¿Dije si conservo o tomo propiedad de lo que recibo?
¿Documenté el comportamiento ante
NULL?¿El nombre de la función sugiere destrucción cuando la hay?
¿La documentación y el
constdicen lo mismo?
Reglas relacionadas¶
0x2003h: Todas las funciones deben incluir documentación completa y estructurada — la documentación estructurada es el marco de esta anotación.
0x3009h: Documentá explícitamente los casos en que una función puede retornar NULL — los retornos
NULLdeben documentarse junto con la propiedad.0x300Eh: Documentá explícitamente el comportamiento de las funciones al manejar punteros nulos como argumentos — el comportamiento frente a argumentos nulos se documenta con el contrato.
0x301Dh: Diseñá los Tipos de Datos Abstractos utilizando punteros opacos — en TAD opacos, la propiedad recae siempre en el módulo.