Menu

typedef em C: nomear tipos, o idioma typedef struct e quando não usar

Como o typedef dá um novo nome a um tipo já existente do C - o idioma typedef struct com e sem tag, typedefs para ponteiros de função e arrays, e o único caso em que um typedef atrapalha: esconder um ponteiro.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

O typedef faz uma coisinha só: dá a um tipo que já existe outro nome. Nenhum tipo novo é criado, nenhuma memória é reservada, e nada sobre como o valor se comporta muda. É uma ferramenta de nomenclatura - e em C, onde os nomes de tipo embutidos são compridos (unsigned long long, struct Employee, void (*)(int)), um bom nome vale muito.

A forma básica

A sintaxe se lê como uma declaração de variável com typedef colado na frente. Onde iria o nome da variável, você põe o novo nome do tipo:

unsigned long  count;    // declara uma variavel chamada count
typedef unsigned long  ulong;   // declara um TIPO chamado ulong

Essa é a regra inteira, e ela explica todo typedef que você vai ler na vida, inclusive os de aparência estranha mais adiante nesta página.

Repare no que o par Celsius/Fahrenheit não compra: segurança de tipos. Os dois são apenas double, então passar um valor em Fahrenheit onde se espera Celsius compila numa boa. Os nomes documentam a intenção para humanos; eles não criam uma verificação.

O idioma typedef struct

Essa é a razão de a maior parte dos programas em C ter algum typedef. Em C o nome do tipo de uma struct inclui a palavra-chave:

struct Point { int x; int y; };
struct Point p;              /* a palavra "struct" todas as vezes */

Um typedef encolhe isso:

Leia com a regra de antes: tire o typedef e você tem struct Point {...} Point;, a declaração de uma variável chamada Point. Devolva o typedef e Point vira um nome de tipo.

Com tag ou sem tag

Você pode deixar a tag totalmente de fora, criando uma struct anônima com apenas o nome do typedef:

typedef struct {
    int x;
    int y;
} Point;          /* nao existe "struct Point" - apenas "Point" */

Isso é mais arrumado, e para um registro de dados simples é o estilo comum. Mas falha no momento em que a struct precisa mencionar a si mesma, porque o nome do typedef não é utilizável até a própria declaração dele terminar:

/* NAO compila: "Node" ainda nao e um tipo dentro das proprias chaves. */
typedef struct {
    int value;
    Node *next;
} Node;

Mantenha a tag e a autorreferência funciona, porque struct Node é utilizável assim que a tag é vista:

A regra prática: mantenha a tag. Ela custa uma palavra, casa com o nome do typedef para que nada fique confuso, e deixa a porta aberta para autorreferência e para declarações antecipadas em cabeçalhos. Veja structs e ponteiros para o que aquele nó encadeado vira.

Declarações antecipadas e tipos opacos

Como um ponteiro para uma struct tem tamanho conhecido mesmo quando o conteúdo da struct é desconhecido, um cabeçalho pode entregar um tipo sem revelar seus membros:

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

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

A struct Stack { ... }; completa mora em stack.c e ninguém de fora pode tocar nos seus membros. Esse padrão de tipo opaco é como bibliotecas em C impõem encapsulamento, e é exatamente o que o FILE é na biblioteca padrão - você recebe um FILE * do fopen e não é informado de nada sobre o que há dentro. Veja manipulação de arquivos.

typedef para enums e unions

O mesmo idioma vale para os outros dois tipos compostos:

Sem o typedef, estes seriam enum Color c; e union Number n;. Veja enums e unions para saber o que eles realmente fazem.

typedef para um ponteiro de função

Aqui o typedef deixa de ser conveniência e fica perto de necessário. A sintaxe crua de um ponteiro de função é notoriamente difícil de ler:

void (*handler)(int, const char *);        /* uma variavel */
void register_cb(void (*cb)(int, const char *));   /* um parametro */

Nomeie o tipo uma vez e os dois ficam legíveis:

Aplique de novo a regra de tirar o typedef: int (*IntBinaryOp)(int, int); sozinho declararia uma variável chamada IntBinaryOp que aponta para uma função que recebe dois ints e devolve um int. Com o typedef, isso vira o nome do tipo. Mais em ponteiros de função.

typedef vs #define

Eles parecem intercambiáveis para apelidos simples e não são:

#define INT_PTR int *
typedef int *int_ptr;

INT_PTR  a, b;    /* expande para: int *a, b;  -- b e um int comum! */
int_ptr  c, d;    /* tanto c quanto d sao int *          */

O #define é substituição cega de texto feita antes de o compilador ver o código; o typedef é uma declaração de verdade que o compilador entende, e ela se aplica a todos os nomes da lista. Use typedef para tipos e reserve #define para constantes e macros.

Quando um typedef atrapalha

Typedefs também podem esconder informação de que o leitor precisa. O exemplo mais nítido é esconder um ponteiro:

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

NodeRef n = get_node();

Olhando para NodeRef n, um leitor não consegue dizer se n pode ser NULL, se precisa ser liberado, se n e uma cópia dele compartilham o mesmo objeto, ou se os membros são alcançados com . ou ->. As quatro perguntas são respondidas na hora por struct Node *n.

A biblioteca padrão concorda: ela faz typedef da struct (FILE) e deixa o asterisco no ponto de uso (FILE *fp). Duas diretrizes razoáveis decorrem disso:

  • Faça typedef de structs, unions, enums e ponteiros de função - o ruído removido não carrega informação.
  • Não faça typedef de um ponteiro só para encurtá-lo, e não faça typedef de um tipo básico para disfarçá-lo (typedef int Boolean; convida alguém a guardar 7 nele - use bool de <stdbool.h>, tratado em booleanos).

Os próprios nomes de largura fixa da biblioteca padrão em <stdint.h> (uint32_t, int64_t, size_t) são todos typedefs, e são o melhor argumento a favor do recurso: dizem exatamente o que são, são portáveis e nada é escondido.

Perguntas frequentes

O que o typedef faz em C?

Ele dá a um tipo já existente um segundo nome. typedef unsigned long ulong; significa que ulong e unsigned long são o mesmo tipo dali em diante. Ele não cria nenhum tipo novo nem nenhum armazenamento novo - apenas uma forma mais curta ou mais descritiva de escrever um que você já tem.

O que é o idioma typedef struct em C?

typedef struct Point { int x; int y; } Point; declara a struct e nomeia o tipo como Point numa instrução só, então você pode escrever Point p; em vez de struct Point p;. Manter a tag (struct Point) importa quando a struct precisa se referir a si mesma, como faz um nó de lista encadeada.

Qual a diferença entre typedef e #define?

O typedef é tratado pelo compilador e cria um apelido de tipo de verdade; o #define é uma substituição de texto feita pelo pré-processador antes da compilação. Essa diferença morde com ponteiros: #define PTR int* faz PTR a, b; expandir para int* a, b; - só a é ponteiro. typedef int *PTR; faz os dois serem ponteiros.

Devo criar um typedef para um tipo ponteiro em C?

Normalmente não. typedef struct Node *NodeRef; esconde o fato de que o tipo é um ponteiro, então o leitor não sabe se a variável pode ser NULL, se precisa ser liberada, ou se precisa de ->. O FILE * da biblioteca padrão mantém o asterisco visível exatamente por essa razão. Faça typedef da struct, não do ponteiro para ela.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR