Menu

typedef en C: nombrar tipos, el modismo typedef struct y cuándo evitarlo

Cómo typedef le da un nombre nuevo a un tipo existente de C: el modismo typedef struct con y sin etiqueta, typedefs para punteros a funciones y arrays, y el único caso donde un typedef perjudica: esconder un puntero.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

typedef hace una sola cosa pequeña: le da a un tipo que ya existe otro nombre. No se crea ningún tipo nuevo, no se aparta memoria y nada de cómo se comporta el valor cambia. Es una herramienta de nombrado, y en C, donde los nombres de tipos incorporados salen largos (unsigned long long, struct Employee, void (*)(int)), un buen nombre vale mucho.

La forma básica

La sintaxis se lee como una declaración de variable con typedef pegado delante. Donde iría el nombre de la variable, pones el nombre del tipo nuevo:

unsigned long  count;           // declara una variable llamada count
typedef unsigned long  ulong;   // declara un TIPO llamado ulong

Esa es toda la regla, y explica cualquier typedef que llegues a leer, incluidos los de aspecto extraño que salen más adelante en esta página.

Fíjate en lo que el par Celsius/Fahrenheit no te da: seguridad de tipos. Ambos son solo double, así que pasar un valor en Fahrenheit donde se espera Celsius compila tan feliz. Los nombres documentan la intención para las personas; no crean ninguna comprobación.

El modismo typedef struct

Esta es la razón por la que la mayoría de los programas de C contienen algún typedef. En C el nombre de tipo de una estructura incluye la palabra clave:

struct Point { int x; int y; };
struct Point p;              /* la palabra "struct" todas y cada una de las veces */

Un typedef colapsa eso:

Léelo con la regla de antes: quita el typedef y tienes struct Point {...} Point;, la declaración de una variable llamada Point. Vuelve a poner typedef y Point pasa a ser un nombre de tipo.

Con etiqueta o sin ella

Puedes dejar fuera la etiqueta por completo, obteniendo una estructura anónima con solo el nombre del typedef:

typedef struct {
    int x;
    int y;
} Point;          /* no existe ningun "struct Point", solo "Point" */

Eso es más pulcro, y para un registro de datos simple es el estilo común. Pero falla en el momento en que la estructura necesita mencionarse a sí misma, porque el nombre del typedef no se puede usar hasta que su propia declaración termina:

/* NO compila: "Node" todavia no es un tipo dentro de sus propias llaves. */
typedef struct {
    int value;
    Node *next;
} Node;

Conserva la etiqueta y la autorreferencia funciona, porque struct Node se puede usar en cuanto se ve la etiqueta:

La regla práctica: conserva la etiqueta. Cuesta una palabra, coincide con el nombre del typedef así que nada resulta confuso, y deja la puerta abierta a la autorreferencia y a las declaraciones adelantadas en las cabeceras. Mira estructuras y punteros para ver en qué se convierte ese nodo enlazado.

Declaraciones adelantadas y tipos opacos

Como un puntero a una estructura tiene un tamaño conocido incluso cuando el contenido de la estructura es desconocido, una cabecera puede entregar un tipo sin revelar sus miembros:

/* stack.h */
typedef struct Stack Stack;     /* declarado, no definido */

Stack *stack_create(void);
void   stack_push(Stack *s, int value);
int    stack_pop(Stack *s);
void   stack_destroy(Stack *s);

La struct Stack { ... }; completa vive en stack.c y nadie de fuera puede tocar sus miembros. Este patrón de tipo opaco es como las bibliotecas de C imponen el encapsulamiento, y es exactamente lo que es FILE en la biblioteca estándar: obtienes un FILE * de fopen y no se te dice nada sobre lo que hay dentro. Mira manejo de archivos.

typedef para enums y uniones

El mismo modismo se aplica a las otras dos clases compuestas:

Sin el typedef serían enum Color c; y union Number n;. Mira enums y uniones para ver lo que hacen realmente.

typedef para un puntero a función

Aquí es donde typedef deja de ser una comodidad y pasa a ser casi necesario. La sintaxis cruda de un puntero a función es célebremente difícil de leer:

void (*handler)(int, const char *);                /* una variable */
void register_cb(void (*cb)(int, const char *));   /* un parametro */

Nombra el tipo una vez y ambos se vuelven legibles:

Aplica otra vez la regla de quitar el typedef: int (*IntBinaryOp)(int, int); por sí sola declararía una variable llamada IntBinaryOp que apunta a una función que toma dos ints y devuelve un int. Con typedef, eso pasa a ser el nombre del tipo. Más en punteros a funciones.

typedef frente a #define

Parecen intercambiables para alias simples y no lo son:

#define INT_PTR int *
typedef int *int_ptr;

INT_PTR  a, b;    /* se expande a: int *a, b;  -- b es un int corriente! */
int_ptr  c, d;    /* tanto c como d son int *          */

#define es sustitución ciega de texto realizada antes de que el compilador vea el código; typedef es una declaración real que el compilador entiende, y se aplica a todos los nombres de la lista. Usa typedef para tipos y reserva #define para constantes y macros.

Cuándo un typedef perjudica

Los typedefs también pueden esconder información que quien lee necesita. El ejemplo más agudo es esconder un puntero:

typedef struct Node *NodeRef;    /* el asterisco desaparece */

NodeRef n = get_node();

Mirando NodeRef n, quien lee no puede saber si n puede ser NULL, si hay que liberarla, si n y una copia suya comparten el mismo objeto o si se llega a los miembros con . o con ->. Las cuatro preguntas quedan respondidas al instante con struct Node *n.

La biblioteca estándar está de acuerdo: hace typedef de la estructura (FILE) y deja el asterisco en el punto de uso (FILE *fp). De ahí salen dos directrices razonables:

  • hagas typedef de estructuras, uniones, enums y punteros a funciones: el ruido que se elimina no lleva información.
  • No hagas typedef de un puntero solo para acortarlo, y no hagas typedef de un tipo básico para disfrazarlo (typedef int Boolean; invita a que alguien guarde un 7 dentro; usa bool de <stdbool.h>, cubierto en booleanos).

Los propios nombres de ancho fijo de la biblioteca estándar en <stdint.h> (uint32_t, int64_t, size_t) son todos typedefs, y son el mejor argumento a favor de esta característica: dicen exactamente lo que son, son portables y no esconden nada.

Preguntas frecuentes

¿Qué hace typedef en C?

Le da a un tipo existente un segundo nombre. typedef unsigned long ulong; significa que ulong y unsigned long son el mismo tipo de ahí en adelante. No crea ningún tipo nuevo ni ningún almacenamiento nuevo: solo una forma más corta o más descriptiva de escribir uno que ya tienes.

¿Qué es el modismo typedef struct en C?

typedef struct Point { int x; int y; } Point; declara la estructura y nombra el tipo Point en una sola sentencia, así que puedes escribir Point p; en lugar de struct Point p;. Conservar la etiqueta (struct Point) importa cuando la estructura necesita referirse a sí misma, como hace un nodo de lista enlazada.

¿Cuál es la diferencia entre typedef y #define?

typedef lo maneja el compilador y crea un alias de tipo real; #define es una sustitución de texto que hace el preprocesador antes de compilar. Esa diferencia muerde con los punteros: #define PTR int* hace que PTR a, b; se expanda a int* a, b;, donde solo a es un puntero. typedef int *PTR; hace punteros a ambos.

¿Debería hacer typedef de un tipo puntero en C?

Normalmente no. typedef struct Node *NodeRef; esconde el hecho de que el tipo es un puntero, así que quien lee no puede saber si una variable puede ser NULL, si hay que liberarla o si necesita ->. El FILE * de la biblioteca estándar mantiene el asterisco visible exactamente por esta razón. Haz typedef de la estructura, no del puntero a ella.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR