Regla 0x301Dh: Diseñá los Tipos de Datos Abstractos utilizando punteros opacos
Memoria, punteros y tipos (0x30XX)
0x301Dh: Diseñá los Tipos de Datos Abstractos utilizando punteros opacos¶
Enunciado normativo¶
DEBE declararse cada Tipo de Dato Abstracto (TAD) como un tipo incompleto en la cabecera pública, y NO DEBE exponerse su representación física (el
structcon sus campos) fuera del archivo.c. La cabecera sólo declaratypedef struct <nombre> <nombre>_t;y las firmas de la interfaz; la definición completa vive en la implementación.
¿Por qué existe esta regla?¶
El problema¶
Un puntero opaco (opaque pointer) apunta a un tipo cuya representación el
cliente desconoce. En C11 el nombre puede declararse sin definirlo
(struct lista_t;) y completarse sólo en el .c; el cliente manipula
lista_t * sin acceder a los campos. Si la cabecera los expone, cualquier
archivo que la incluya puede leerlos y escribirlos, rompe el invariante interno
y desincroniza el TAD.
Consecuencias de violarla¶
| Tipo de consecuencia | Efecto concreto |
|---|---|
| Invariantes rotos | El cliente modifica cabeza, cantidad o capacidad directamente y desincroniza el TAD. |
| Mantenibilidad | Cambiar un campo obliga a recompilar todos los clientes y a revisar cada acceso directo. |
| Acoplamiento | La lógica de la estructura se filtra por todo el programa. |
Fundamento en el estándar y en la cátedra¶
C11 §6.2.5 permite declarar punteros a tipos incompletos; el tipo se completa dentro de la unidad de traducción que lo define. La cátedra adopta el puntero opaco porque fuerza a que toda la manipulación pase por las funciones de interfaz, únicas que pueden documentar y verificar el contrato.
Alcance y excepciones¶
Aplica a todo TAD con estado compartido: pilas, colas, listas, árboles, buffers.
No aplica a estructuras de datos planas que se usan como registro de paso
(struct punto_t { int x; int y; };), donde exponer campos es legítimo porque no
hay invariante que proteger.
Ejemplos exhaustivos¶
❌ Contraejemplo 1 — Cabecera que expone los campos internos¶
/* lista.h */
typedef struct lista_t
{
struct nodo *cabeza;
size_t cantidad;
} lista_t;Por qué falla: cualquiera que incluya lista.h puede escribir
lista->cantidad = 0; sin vaciar los nodos. El invariante “cantidad coincide
con la longitud real” deja de estar garantizado por el módulo y pasa a depender
de la disciplina de cada cliente.
❌ Contraejemplo 2 — Tipo opaco falso mediante void *¶
/* lista.h */
typedef void *lista_t;
lista_t lista_crear(void);
void lista_agregar(lista_t lista, int valor);Por qué falla: void * oculta los campos, pero también borra la información de
tipo. lista_agregar(otro_puntero, 3) compila sin diagnóstico y el error se
manifiesta como corrupción de memoria en ejecución. El tipo incompleto
struct lista_t conserva la verificación de tipos que void * pierde.
✅ Ejemplo conforme 1 — Cabecera con tipo incompleto¶
/* lista.h */
#ifndef LISTA_H
#define LISTA_H
#include <stddef.h>
typedef struct lista_t lista_t;
lista_t *lista_crear(void);
void lista_agregar(lista_t *lista, int valor);
size_t lista_cantidad(const lista_t *lista);
void lista_destruir(lista_t *lista);
#endif/* lista.c */
#include "lista.h"
#include <stdlib.h>
struct lista_t
{
struct nodo *cabeza;
size_t cantidad;
};El cliente compila contra la declaración incompleta; sólo el módulo conoce la
representación. sizeof(lista_t) es un error de compilación en el cliente, lo
cual es exactamente lo deseado.
✅ Ejemplo conforme 2 — Interacción con acceso de sólo lectura¶
size_t lista_cantidad(const lista_t *lista)
{
if (lista == NULL)
{
return 0;
}
return lista->cantidad;
}La función lista_cantidad recibe const lista_t *: el cliente no puede
modificar el TAD y el módulo conserva la potestad sobre los campos. Combina la
opacidad con el contrato de sólo lectura de 0x3007h: Los argumentos de tipo puntero deben ser const siempre que la función no los modifique.
⚠️ Casos límite¶
Pruebas unitarias: para inspeccionar el interior, agregá funciones de diagnóstico al módulo (por ejemplo
lista_verificar), en lugar de abrir la cabecera para los tests.sizeof: nunca puede aplicarse al tipo incompleto; si el cliente necesita el tamaño, la interfaz debe exponerlo con una función.
Cómo detectarla¶
| Herramienta | Comando | Señal |
|---|---|---|
gaff | gaff check archivo.c | Campos de struct de TAD visibles en el .h. |
gcc / clang | gcc -Wall -Wextra -std=c11 -c cliente.c | sizeof(lista_t) o acceso a campos da error de tipo incompleto. |
Checklist de autocontrol¶
¿La cabecera declara el tipo sólo con
typedef struct X X_t;?¿Todo acceso a campos ocurre dentro del módulo?
¿Existe una función constructora y otra destructora documentadas?
Reglas relacionadas¶
0x301Eh: Asigná NULL al puntero tras liberar un recurso opaco en el ámbito del cliente — el puntero opaco se anula en el cliente tras destruirlo.
0x3006h: Documentá la propiedad de los recursos al utilizar punteros — la opacidad obliga a documentar quién libera el recurso.
0x3004h: Utilizá typedef para definir tipos de estructuras con el sufijo _t — el alias del TAD debe terminar en
_t.