Uma struct passada a uma função é copiada. Isso tudo bem para um ponto de dois ints e é desperdício para um registro de 200 bytes - e torna a alteração impossível, porque a função só enxerga a própria cópia. Apontar para uma struct resolve os dois problemas de uma vez, e é também a única maneira de construir estruturas que crescem: listas, árvores, grafos.
Pegando um ponteiro para uma struct
Nada de especial é necessário. O & dá o endereço, e o tipo é "ponteiro para struct":
Os três imprimem 3. Os dois últimos são a mesma operação escrita de duas formas.
Por que (*ptr).x precisa dos parênteses
Você pode tentar tirá-los e escrever *ptr.x. Isso compila para algo bem diferente ou, com mais frequência, se recusa a compilar - porque o operador de membro . tem precedência mais alta que a desreferência *:
*ptr.x // interpretado como *(ptr.x) -- errado
(*ptr).x // desreferencia primeiro, depois pega o membro -- correto
ptr->x // a mesma coisa, dita de forma curta
ptr.x pede um membro do ponteiro, e um ponteiro não tem membros. A reclamação do compilador ("request for member 'x' in something not a structure or union") é um bug de precedência disfarçado.
Como essa forma com parênteses é feia e fácil de errar, o C fornece o ->. Na prática você quase nunca verá (*p).x em código real; recorra a p->x e esqueça que a forma longa existe, exceto como explicação do que a seta significa.
Passando uma struct por ponteiro
Um parâmetro ponteiro dá à função a struct real do chamador, então as mudanças permanecem:
Dois hábitos que vale formar aqui:
- Marque ponteiros somente leitura como
const.const struct Point *ppromete que a função não vai mudar aquilo para quepaponta. Se alguém depois acrescentarp->x = 0lá dentro, a compilação falha em vez de o bug ir para produção. Também documenta a intenção no ponto de chamada. - Passe um ponteiro para structs grandes mesmo ao apenas ler. Uma struct com um buffer de nome de 64 bytes custa 64 bytes de cópia por chamada por valor; um ponteiro custa 8.
Structs pequenas ainda podem ser passadas por valor - uma struct Point de 8 bytes copia tão barato quanto um ponteiro, e a versão por valor não pode ser NULL, o que elimina um modo de falha inteiro.
Ponteiros dentro de arrays de structs
A aritmética de ponteiros funciona em arrays de struct exatamente como em arrays de int: p + 1 avança uma struct inteira, incluindo o preenchimento.
O nome de um array decai para um ponteiro ao seu primeiro elemento, então staff já é um struct Employee * na chamada. É também por isso que a função precisa receber n à parte - o sizeof dentro da função mediria o ponteiro, não o array.
Alocando uma struct no heap
Structs na pilha morrem no fim do seu escopo. Para fazer uma que sobreviva à função que a criou - ou para fazer quantas a entrada exigir - aloque com malloc:
Quatro detalhes dessa pequena função são todos essenciais:
sizeof *e, nãosizeof(struct Employee). Isso se lê como "o tamanho daquilo para queeaponta", então, se o tipo mudar algum dia, a alocação acompanha automaticamente. Não há como os dois se desencontrarem.- Verifique o
NULL. OmallocdevolveNULLquando não consegue atender ao pedido. Escrevere->idpor um ponteiro nulo é uma falha de segmentação. - Inicialize todos os membros. O
mallocnão zera a memória; a struct chega cheia de lixo. (calloc(1, sizeof *e)a zera para você.) - Alguém precisa chamar
free. Devolver um ponteiro alocado transfere esse dever ao chamador. Diga isso num comentário - uma regra de propriedade pouco clara é como começam os vazamentos de memória.
A recompensa: um nó de lista encadeada
Eis o que structs não conseguem fazer sem ponteiros. Uma struct não pode conter a si mesma - isso seria infinitamente grande - mas pode conter um ponteiro para uma da sua própria espécie, e esse único truque constrói toda estrutura de dados encadeada em C.
Repare que struct Node *next; se refere a struct Node enquanto struct Node ainda está sendo declarada. Isso é legal justamente porque um ponteiro tem tamanho conhecido independentemente daquilo para que aponta - o compilador ainda não precisa da definição completa. É também o único lugar em que você precisa usar a tag struct Node mesmo tendo escrito um typedef: o nome do typedef ainda não existe dentro da própria definição.
O laço de free_list salva head->next antes de chamar free(head). Ler head->next depois da liberação é um uso-após-liberação - a memória não é mais sua, e o valor lido é o que o alocador tiver colocado ali.
Erros comuns
p->xnum ponteiro não inicializado ouNULL. A causa mais comum de travamento em código cheio de structs. Inicialize ponteiros comNULLe verifique antes de desreferenciar.- Devolver um ponteiro para uma struct local.
struct Point *bad(void) { struct Point p = {1,2}; return &p; }devolve o endereço de memória que deixa de existir no instante em que a função retorna. Devolva a struct por valor, ou aloque-a. - Liberar duas vezes, ou esquecer de liberar. Cada
malloccasa com exatamente umfree. Depois de liberar, coloqueNULLno ponteiro para que um uso acidental posterior trave alto em vez de corromper a memória em silêncio. - Copiar uma struct que contém um ponteiro. A atribuição de struct copia o valor do ponteiro, não aquilo para que ele aponta. As duas structs agora referenciam o mesmo buffer, e a primeira que o liberar deixa a outra pendurada.
Perguntas frequentes
O que o operador -> faz em C?
-> faz em C?p->x acessa o membro x através do ponteiro p. É exatamente equivalente a (*p).x - desreferenciar o ponteiro e depois pegar o membro. A seta existe porque ponteiros para structs são tão comuns que a forma longa polui o código.
Por que *p.x não funciona com um ponteiro de struct?
*p.x não funciona com um ponteiro de struct?Porque o . liga mais forte que o *. *p.x é interpretado como *(p.x) - pegar o membro x de p (que é um ponteiro, não uma struct) e desreferenciar isso. Você precisa de (*p).x, ou simplesmente escreva p->x.
Devo passar uma struct por valor ou por ponteiro em C?
Passe por ponteiro quando a função precisa modificar a struct do chamador, ou quando a struct é grande e copiá-la seria desperdício. Passe por valor para structs pequenas e somente leitura, em que a cópia é barata e o código é mais simples. Marque parâmetros de ponteiro somente leitura como const struct T * para que o compilador imponha a promessa.
Como alocar uma struct com malloc em C?
struct Node *n = malloc(sizeof *n); - a forma sizeof *n pede o tamanho daquilo para que n aponta, então continua correta se você mudar o tipo depois. Verifique o resultado contra NULL, inicialize todos os membros e chame free(n) ao terminar.