C Structs e Ponteiros (->)

Na aula de Structs, uma função que recebia um struct Aluno trabalhava numa cópia: o bônus na nota sumia quando ela terminava. Agora você vai juntar structs com ponteiros. Com o endereço da struct, uma função consegue mudar a original, structs enormes deixam de ser copiadas a cada chamada, e dá para criar structs com malloc. Tudo isso com um operador novo, que você vai ver em praticamente todo programa C: a seta, ->.

Você vai aprender
  • Como apontar para uma struct e acessar os campos com (*p).nota e com a seta p->nota.
  • Por que *p.nota não funciona (precedência).
  • Como uma função muda uma struct de fora recebendo struct Aluno *.
  • Por que passar structs grandes por ponteiro, e como const struct Aluno * protege a original.
  • Como criar structs com malloc(sizeof *p) e arrays de structs com calloc.
  • Como a lista que cresce, da aula Array que Cresce, fica bem mais limpa com uma struct.

Todos os programas desta aula foram compilados com o gcc 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/c, com o comando padrão do guia: gcc -std=c23 -Wall -Wextra arquivo.c -o programa. A struct de exemplo é a mesma da aula anterior: struct Aluno { char nome[50]; int idade; double nota; };.


A ideia: o número da gaveta

A secretaria da escola guarda as fichas dos alunos em gavetas numeradas. Quando a professora precisa ver a ficha da Ana, a secretaria pode tirar uma xerox (dá trabalho, e anotar na xerox não muda a ficha de verdade) ou simplesmente dizer: "está na gaveta 42". Com o número da gaveta, a professora lê a ficha original e, se puder, corrige a nota lá mesmo.

Na secretariaEm C
Tirar uma xerox da fichapassar a struct por valor: void f(struct Aluno a)
Dar o número da gavetapassar o endereço: f(&ana), com void f(struct Aluno *a)
Ir até a gaveta e ler o campo "Nota"a->nota
"Pode olhar, mas não escreva"const struct Aluno *a

1. Ponteiro para struct e a seta

Um ponteiro para struct funciona como qualquer ponteiro: guarda um endereço, que você pega com &. Para chegar num campo, há duas escritas que fazem exatamente a mesma coisa:

#include <stdio.h>

struct Aluno {
    char nome[50];
    int idade;
    double nota;
};

int main(void) {
    struct Aluno ana = {"Ana", 20, 8.5};
    struct Aluno *p = &ana;

    printf("%s\n", (*p).nome);
    printf("%s\n", p->nome);

    p->nota = 10.0;
    printf("Nota da Ana: %.1f\n", ana.nota);
    return 0;
}
struct Aluno *p = &ana;
p é um "ponteiro para struct Aluno" e guarda o endereço da ana. O * na declaração diz "é um ponteiro".
(*p).nome
Primeiro *p segue o endereço e chega na struct ana; depois .nome pega o campo. Os parênteses são obrigatórios: o ponto é mais forte que o *.
p->nome
A seta (um hífen e um sinal de maior, sem espaço) é um atalho para (*p).nome. Mesma coisa, menos símbolos. É a forma que todo mundo usa.
p->nota = 10.0;
Muda o campo da própria ana, porque p aponta para ela. Não existe cópia nenhuma aqui.
$ gcc -std=c23 -Wall -Wextra seta.c -o seta
$ ./seta
Ana
Ana
Nota da Ana: 10.0

Regra para lembrar: tem a struct → ponto (ana.nota); tem um ponteiro para a struct → seta (p->nota).

Por que não *p.nota?

Sem os parênteses, o C lê *p.nota como *(p.nota): "pegue o campo nota de p e siga esse endereço". Mas p é um ponteiro, não uma struct, e não tem campos:

$ gcc -std=c23 -Wall -Wextra semparen.c -o semparen
semparen.c: In function ‘main’:
semparen.c:12:24: error: ‘p’ is a pointer; did you mean to use ‘->’?
   12 |     printf("%.1f\n", *p.nota);
      |                        ^
      |                        ->
«…»

‘p’ is a pointer; did you mean to use ‘->’? = "p é um ponteiro; você quis usar ->?". O gcc até mostra onde pôr a seta. Foi justamente para fugir dessa armadilha de precedência (veja Precedência) que a seta foi inventada.


2. Uma função que muda a struct

Agora o problema do bônus perdido tem solução. A função recebe o endereço da struct e mexe nela pela seta, como você viu com variáveis simples em Passar por Ponteiro:

#include <stdio.h>

struct Aluno {
    char nome[50];
    int idade;
    double nota;
};

void dar_bonus(struct Aluno *a, double pontos) {
    a->nota += pontos;
}

void mostrar(const struct Aluno *a) {
    printf("%s (%d anos): %.1f\n", a->nome, a->idade, a->nota);
}

int main(void) {
    struct Aluno ana = {"Ana", 20, 6.5};
    mostrar(&ana);
    dar_bonus(&ana, 1.0);
    mostrar(&ana);
    return 0;
}
LinhaO que faz
void dar_bonus(struct Aluno *a, double pontos)Recebe o endereço de uma struct. A função pode mudar a original.
a->nota += pontos;Soma os pontos na nota da struct de fora (a do main).
void mostrar(const struct Aluno *a)Também recebe o endereço (nada de copiar 64 bytes), mas o const promete só olhar. A seção 3 mostra o que o const impede.
dar_bonus(&ana, 1.0);Na chamada, o & pega o endereço da ana.
$ gcc -std=c23 -Wall -Wextra bonus.c -o bonus
$ ./bonus
Ana (20 anos): 6.5
Ana (20 anos): 7.5          « agora o bônus ficou

Missão: escreva o corpo da função aniversario, que soma 1 na idade do aluno recebido. Use a seta. A tela deve mostrar Ana agora tem 21 anos.

#include <stdio.h>

struct Aluno {
    char nome[50];
    int idade;
    double nota;
};

void aniversario(struct Aluno *a) {
    /* some 1 na idade de a, usando a seta */
}

int main(void) {
    struct Aluno ana = {"Ana", 20, 8.5};
    aniversario(&ana);
    printf("%s agora tem %d anos\n", ana.nome, ana.idade);
    return 0;
}
#include <stdio.h>

struct Aluno {
    char nome[50];
    int idade;
    double nota;
};

void aniversario(struct Aluno *a) {
    a->idade++;
}

int main(void) {
    struct Aluno ana = {"Ana", 20, 8.5};
    aniversario(&ana);
    printf("%s agora tem %d anos\n", ana.nome, ana.idade);
    return 0;
}

Rodando o código inicial, o Clang avisa unused parameter 'a' ("parâmetro a não usado"): a função recebe o aluno e não faz nada com ele. E repare: a->idade++ funciona sem parênteses, porque a seta é mais forte que o ++.


3. Structs grandes e o const

Passar uma struct por valor copia todos os bytes dela. Para um Aluno de 64 bytes, tudo bem. Mas structs podem ser enormes. Uma turma com 30 alunos dentro:

#include <stdio.h>

struct Aluno {
    char nome[50];
    int idade;
    double nota;
};

struct Turma {
    char sala[10];
    struct Aluno alunos[30];
    int quantos;
};

double media_copia(struct Turma t) {
    double soma = 0.0;
    for (int i = 0; i < t.quantos; i++) {
        soma += t.alunos[i].nota;
    }
    return t.quantos > 0 ? soma / t.quantos : 0.0;
}

double media(const struct Turma *t) {
    double soma = 0.0;
    for (int i = 0; i < t->quantos; i++) {
        soma += t->alunos[i].nota;
    }
    return t->quantos > 0 ? soma / t->quantos : 0.0;
}

int main(void) {
    struct Turma t = {.sala = "8A", .quantos = 3};
    t.alunos[0] = (struct Aluno){"Ana", 20, 8.5};
    t.alunos[1] = (struct Aluno){"Bruno", 19, 6.0};
    t.alunos[2] = (struct Aluno){"Carla", 22, 9.5};

    printf("Uma struct Turma ocupa %zu bytes\n", sizeof(struct Turma));
    printf("Um ponteiro ocupa %zu bytes\n", sizeof(struct Turma *));
    printf("Média (cópia):    %.2f\n", media_copia(t));
    printf("Média (ponteiro): %.2f\n", media(&t));
    return 0;
}
$ gcc -std=c23 -Wall -Wextra grande.c -o grande
$ ./grande
Uma struct Turma ocupa 1944 bytes
Um ponteiro ocupa 8 bytes
Média (cópia):    8.00
Média (ponteiro): 8.00

As duas funções dão o mesmo resultado. Mas cada chamada de media_copia copia 1944 bytes; cada chamada de media passa só 8 bytes (um endereço). Num laço que chama a função milhares de vezes, a diferença aparece. Repare em t->alunos[i].nota: seta para entrar na turma (que é um ponteiro), colchetes para escolher o aluno e ponto para o campo (porque alunos[i] já é uma struct, não um ponteiro).

No editor do guia, o ponteiro tem 4 bytes

O editor roda em WebAssembly de 32 bits, onde endereços ocupam 4 bytes, e não 8 como no gcc de 64 bits. A ideia é a mesma: o ponteiro é minúsculo perto da struct.

O const protege a original

Passar por ponteiro tem um risco: a função pode mudar a original, mesmo quando só devia ler. O const fecha essa porta. Se uma função recebe const struct Aluno * e tenta mudar um campo, o compilador barra:

void mostrar(const struct Aluno *a) {
    a->nota = 10.0;
    printf("%s: %.1f\n", a->nome, a->nota);
}
$ gcc -std=c23 -Wall -Wextra constmuda.c -o constmuda
constmuda.c: In function ‘mostrar’:
constmuda.c:10:13: error: assignment of member ‘nota’ in read-only object
   10 |     a->nota = 10.0;
      |             ^

assignment of member ‘nota’ in read-only object = "atribuição ao campo nota de um objeto somente leitura". Se a função deve mudar a ficha, tire o const; se não deve, o erro acabou de achar um engano seu. A regra prática: struct que a função só lê → const struct X *; struct que a função muda → struct X *. Mais detalhes em const e Ponteiros.


4. Structs no heap: malloc

Uma struct também pode morar no heap, reservada com malloc. É assim quando uma função precisa criar uma struct que continue existindo depois que ela termina. O resultado é um ponteiro, então tudo é com seta:

#include <stdio.h>
#include <stdlib.h>

struct Aluno {
    char nome[50];
    int idade;
    double nota;
};

struct Aluno *novo_aluno(const char *nome, int idade, double nota) {
    struct Aluno *a = malloc(sizeof *a);
    if (a == NULL) {
        return NULL;
    }
    snprintf(a->nome, sizeof a->nome, "%s", nome);
    a->idade = idade;
    a->nota = nota;
    return a;
}

int main(void) {
    struct Aluno *eva = novo_aluno("Eva", 18, 7.0);
    if (eva == NULL) {
        printf("Sem memória!\n");
        return 1;
    }
    printf("%s, %d anos, nota %.1f\n", eva->nome, eva->idade, eva->nota);
    free(eva);
    return 0;
}
struct Aluno *a = malloc(sizeof *a);
Reserva espaço para uma struct. O sizeof *a quer dizer "o tamanho daquilo para onde a aponta", ou seja, sizeof(struct Aluno). É o jeito preferido: se um dia o tipo de a mudar, o tamanho acompanha sozinho. (Cuidado: sizeof a, sem o *, é o tamanho do ponteiro; veja "Quando dá errado".)
if (a == NULL) return NULL;
Confira sempre o malloc.
snprintf(a->nome, sizeof a->nome, "%s", nome);
Copia o nome sem passar dos 50 bytes do campo.
return a;
Devolve o endereço. A struct continua viva no heap depois que a função termina, e quem recebeu é dono dela.
free(eva);
Um malloc, um free.
$ gcc -std=c23 -Wall -Wextra -g novo.c -o novo
$ ./novo
Eva, 18 anos, nota 7.0
$ valgrind --leak-check=full ./novo 2>&1 | grep -E "Eva|heap usage|All heap|ERROR SUM"
Eva, 18 anos, nota 7.0
==72414==   total heap usage: 2 allocs, 2 frees, 4,160 bytes allocated
==72414== All heap blocks were freed -- no leaks are possible
==72414== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Dois blocos: os 64 bytes da Eva e o buffer de saída do printf. Os dois liberados, nenhum erro.

Um array de structs no heap

Para uma turma cujo tamanho só se sabe na hora, reserve n structs de uma vez. Com calloc, todas já nascem zeradas (veja calloc e realloc):

#include <stdio.h>
#include <stdlib.h>

struct Aluno {
    char nome[50];
    int idade;
    double nota;
};

int main(void) {
    size_t n = 3;
    struct Aluno *turma = calloc(n, sizeof *turma);
    if (turma == NULL) {
        return 1;
    }
    turma[0] = (struct Aluno){"Ana", 20, 8.5};
    turma[1] = (struct Aluno){"Bruno", 19, 6.0};
    (turma + 2)->nota = 9.5;
    snprintf(turma[2].nome, sizeof turma[2].nome, "Carla");

    for (size_t i = 0; i < n; i++) {
        printf("%s: %.1f\n", turma[i].nome, turma[i].nota);
    }
    free(turma);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra turma.c -o turma
$ ./turma
Ana: 8.5
Bruno: 6.0
Carla: 9.5

Atenção a um detalhe que confunde: turma é um ponteiro, mas turma[2] já é uma struct (o colchete segue o endereço, veja Ponteiros e Arrays). Por isso é turma[2].nome, com ponto. A forma (turma + 2)->nota chega no mesmo lugar por outro caminho: turma + 2 é o endereço do terceiro aluno, e a seta entra nele. Prefira os colchetes, que são mais fáceis de ler.


5. A lista que cresce, agora com struct

Na aula Array que Cresce, a lista de preços precisava de três variáveis soltas e de funções com parâmetros como double **precos e (*precos)[*tamanho]. Ficou prometido que a struct ia arrumar isso. Aqui está: as três variáveis viram uma struct Lista, e as funções recebem um ponteiro para ela.

#include <stdio.h>
#include <stdlib.h>

struct Lista {
    double *dados;
    size_t tamanho;
    size_t capacidade;
};

bool adicionar(struct Lista *l, double valor) {
    if (l->tamanho == l->capacidade) {
        size_t nova = (l->capacidade == 0) ? 4 : l->capacidade * 2;
        double *maior = realloc(l->dados, nova * sizeof *maior);
        if (maior == NULL) {
            return false;
        }
        l->dados = maior;
        l->capacidade = nova;
    }
    l->dados[l->tamanho] = valor;
    l->tamanho++;
    return true;
}

void liberar(struct Lista *l) {
    free(l->dados);
    *l = (struct Lista){0};
}

int main(void) {
    struct Lista precos = {0};

    double preco;
    while (scanf("%lf", &preco) == 1) {
        if (!adicionar(&precos, preco)) {
            printf("Sem memória!\n");
            break;
        }
    }

    double total = 0.0;
    for (size_t i = 0; i < precos.tamanho; i++) {
        total += precos.dados[i];
    }
    printf("%zu itens (capacidade %zu)\n", precos.tamanho, precos.capacidade);
    printf("Total: R$ %.2f\n", total);

    liberar(&precos);
    return 0;
}
Antes (variáveis soltas)Agora (struct)
double *precos = NULL; size_t tamanho = 0; size_t capacidade = 0;struct Lista precos = {0};
adicionar(double **precos, size_t *tamanho, size_t *capacidade, double valor)adicionar(struct Lista *l, double valor)
(*precos)[*tamanho] = valor;l->dados[l->tamanho] = valor;
(*tamanho)++;l->tamanho++;
adicionar(&precos, &tamanho, &capacidade, preco)adicionar(&precos, preco)
Três linhas para zerar no liberar*l = (struct Lista){0}; (um literal composto zerado copiado por cima)
$ gcc -std=c23 -Wall -Wextra -g lista.c -o lista
$ ./lista < compras.txt
10 itens (capacidade 16)
Total: R$ 72.73
$ valgrind --leak-check=full ./lista < compras.txt 2>&1 | tail -5
==72473==
==72473== All heap blocks were freed -- no leaks are possible
==72473==
==72473== For lists of detected and suppressed errors, rerun with: -s
==72473== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

O compras.txt é o mesmo da aula Array que Cresce, com dez preços. A lógica é idêntica à da outra aula (começa em 4, dobra, confere o realloc, libera no fim); o resultado também. Mudou só a organização: o que anda junto fica junto. Esse padrão (uma struct com os dados e funções que recebem um ponteiro para ela) é o jeito C de criar "tipos com comportamento", e você vai usá-lo nas listas ligadas, pilhas e filas e em todo projeto maior.

Uma struct que aponta para outra do mesmo tipo

Uma struct pode ter um campo que é ponteiro para o próprio tipo: struct No { int valor; struct No *proximo; };. Cada nó sabe onde está o próximo, como os vagões de um trem. Não pode ter uma struct No inteira dentro de si (ela teria tamanho infinito), mas um ponteiro para ela, sim. Essa é a base das Listas Ligadas.


Ponto ou seta? Por valor ou por ponteiro? Qual usar

Você tem…UseExemplo
uma variável structpontoana.nota
um ponteiro para structsetap->nota
um elemento de array de structs (mesmo que o array venha de malloc)colchetes e pontoturma[i].nota
um ponteiro para struct que tem uma struct dentroseta, depois pontop->nascimento.dia
ParâmetroO que aconteceQuando usar
const struct Aluno *aA função olha a original, sem copiar e sem poder mudar.O padrão para structs que a função só lê.
struct Aluno *aA função mexe na original.Quando o objetivo é mudar (dar_bonus, adicionar).
struct Aluno aA função recebe uma cópia inteira.Structs pequenas (uma struct Data de 12 bytes, um ponto {x, y}) quando a função quer uma cópia para mexer à vontade.

Quando dá errado

"‘p’ is a pointer; did you mean to use ‘->’?" e "invalid type argument of ‘->’"
    struct Aluno ana = {"Ana", 20, 8.5};
    struct Aluno *p = &ana;
    printf("%.1f\n", p.nota);       /* ponto num ponteiro */
    printf("%.1f\n", ana->nota);    /* seta numa struct */
$ gcc -std=c23 -Wall -Wextra pontoponteiro.c -o pontoponteiro
pontoponteiro.c: In function ‘main’:
pontoponteiro.c:12:23: error: ‘p’ is a pointer; did you mean to use ‘->’?
   12 |     printf("%.1f\n", p.nota);
      |                       ^
      |                       ->
pontoponteiro.c:13:25: error: invalid type argument of ‘->’ (have ‘struct Aluno’)
   13 |     printf("%.1f\n", ana->nota);
      |                         ^~
«…»

O que significa: o primeiro erro é o ponto usado num ponteiro (o gcc sugere a seta). O segundo é o contrário: invalid type argument of ‘->’ (have ‘struct Aluno’) = "argumento de tipo inválido para -> (é uma struct Aluno)": a seta só funciona com ponteiros.

Como resolver: olhe a declaração da variável. Tem * (struct Aluno *p)? Seta. Não tem? Ponto.

"assignment of member ‘nota’ in read-only object"

O que aconteceu: a função recebeu const struct Aluno * e tentou mudar um campo (seção 3).

Como resolver: se a função deve mudar a struct, tire o const do parâmetro; se não deve, apague a linha que muda.

Seta num ponteiro NULL: "Falha de segmentação"
#include <stdio.h>
#include <string.h>

struct Aluno {
    char nome[50];
    int idade;
    double nota;
};

struct Aluno *procurar(struct Aluno turma[], size_t n, const char *nome) {
    for (size_t i = 0; i < n; i++) {
        if (strcmp(turma[i].nome, nome) == 0) {
            return &turma[i];
        }
    }
    return NULL;
}

int main(void) {
    struct Aluno turma[] = {{"Ana", 20, 8.5}, {"Bruno", 19, 6.0}};
    struct Aluno *p = procurar(turma, 2, "Bruno");
    printf("Nota do Bruno: %.1f\n", p->nota);
    p = procurar(turma, 2, "Zeca");
    printf("Nota do Zeca: %.1f\n", p->nota);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra -g nulo.c -o nulo
$ ./nulo
Nota do Bruno: 6.0
Falha de segmentação     (imagem do núcleo gravada) ./nulo

O que aconteceu: não existe Zeca na turma, e procurar devolveu NULL, o "endereço nenhum". O p->nota tentou ler um campo no endereço nenhum, e o sistema derrubou o programa. No editor do guia acontece o mesmo: Falha de segmentação (imagem do núcleo gravada), código 139.

Como resolver: toda função que pode devolver NULL (malloc, procurar, fopen…) pede um if (p == NULL) antes da seta. Veja Ponteiro Nulo.

malloc(sizeof a): "allocation of insufficient size"
    struct Aluno *a = malloc(sizeof a);      /* faltou o * */
$ gcc -std=c23 -Wall -Wextra -g tamanho.c -o tamanho
tamanho.c: In function ‘novo_aluno’:
tamanho.c:11:23: warning: allocation of insufficient size ‘8’ for type ‘struct Aluno’ with size ‘64’ [-Walloc-size]
   11 |     struct Aluno *a = malloc(sizeof a);
      |                       ^~~~~~
$ gcc -std=c23 -Wall -Wextra -g -fsanitize=address tamanho.c -o tamanho
$ ./tamanho
=================================================================
==72752==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7321299e0044 at pc 0x5913c9918362 bp 0x7ffc4dbffc20 sp 0x7ffc4dbffc10
WRITE of size 4 at 0x7321299e0044 thread T0
    #0 0x5913c9918361 in novo_aluno /home/ana/c/tamanho.c:16
    #1 0x5913c99183d0 in main /home/ana/c/tamanho.c:22
«…»
0x7321299e0044 is located 44 bytes after 8-byte region [0x7321299e0010,0x7321299e0018)
«…»

O que significa: sizeof a é o tamanho do ponteiro a (8 bytes), não da struct (64). O gcc 15 percebe e avisa: allocation of insufficient size ‘8’ for type ‘struct Aluno’ with size ‘64’ = "reserva de tamanho insuficiente: 8 bytes para um tipo de 64". O sanitizer mostra a consequência: a linha 16 (a->idade = idade;) escreveu 44 bytes depois do fim do bloco de 8. No editor do guia, o Clang dá o mesmo aviso (com '4', o tamanho do ponteiro lá), e o programa pode até mostrar a Eva certinha: um estouro de memória calado.

Como resolver: malloc(sizeof *a), com o asterisco: "o tamanho daquilo para onde a aponta". Ou malloc(sizeof(struct Aluno)).


Referência rápida

EscritaSignificado
struct Aluno *p = &ana;Ponteiro para uma struct que já existe.
p->notaCampo pela seta; o mesmo que (*p).nota.
*p.notaErro: é lido como *(p.nota).
void f(struct Aluno *a)A função pode mudar a struct de fora; chame com f(&ana).
void f(const struct Aluno *a)A função só lê, sem copiar.
struct Aluno *a = malloc(sizeof *a);Uma struct no heap (confira NULL; termine com free(a)).
struct Aluno *t = calloc(n, sizeof *t);n structs zeradas no heap; acesse com t[i].nota.
p->nascimento.diaStruct dentro de struct, a partir de um ponteiro.
*p = (struct Lista){0};Zera a struct apontada inteira de uma vez.
struct No { int valor; struct No *proximo; };Struct com ponteiro para o próprio tipo (listas ligadas).

Exercícios

1. A seta

p é um struct Aluno *. Complete com a seta para mostrar a idade:

printf("%d\n", );

2. Sem a seta

Escreva p->nota da outra forma, com * e ponto:

3. Reservar uma struct

Complete para reservar uma struct Aluno no heap, do jeito que acompanha o tipo do ponteiro:

struct Aluno *novo = malloc();

4. Uma função só precisa mostrar uma struct grande. Qual parâmetro é o melhor?

5. turma veio de calloc(n, sizeof *turma). Como se lê o nome do primeiro aluno?

6. O que sizeof p mede, quando p é um struct Aluno *?

Missão final: conserte a fábrica de alunos

Missão: a função novo_aluno devolve o endereço de uma variável local, que deixa de existir quando a função termina (o Clang avisa). No gcc, esse programa diz Sem memória!; no editor, parece funcionar por sorte. Conserte: reserve a struct com malloc(sizeof *a), confira o NULL e preencha os campos com a seta. O programa deve mostrar Eva, 18 anos, nota 7.0 e compilar sem nenhum aviso.

#include <stdio.h>
#include <stdlib.h>

struct Aluno {
    char nome[50];
    int idade;
    double nota;
};

struct Aluno *novo_aluno(const char *nome, int idade, double nota) {
    struct Aluno a = {0};
    snprintf(a.nome, sizeof a.nome, "%s", nome);
    a.idade = idade;
    a.nota = nota;
    return &a;
}

int main(void) {
    struct Aluno *eva = novo_aluno("Eva", 18, 7.0);
    if (eva == NULL) {
        printf("Sem memória!\n");
        return 1;
    }
    printf("%s, %d anos, nota %.1f\n", eva->nome, eva->idade, eva->nota);
    free(eva);
    return 0;
}
#include <stdio.h>
#include <stdlib.h>

struct Aluno {
    char nome[50];
    int idade;
    double nota;
};

struct Aluno *novo_aluno(const char *nome, int idade, double nota) {
    struct Aluno *a = malloc(sizeof *a);
    if (a == NULL) {
        return NULL;
    }
    snprintf(a->nome, sizeof a->nome, "%s", nome);
    a->idade = idade;
    a->nota = nota;
    return a;
}

int main(void) {
    struct Aluno *eva = novo_aluno("Eva", 18, 7.0);
    if (eva == NULL) {
        printf("Sem memória!\n");
        return 1;
    }
    printf("%s, %d anos, nota %.1f\n", eva->nome, eva->idade, eva->nota);
    free(eva);
    return 0;
}

Resumo

  • Um ponteiro para struct guarda o endereço dela: struct Aluno *p = &ana;.
  • p->nota é o mesmo que (*p).nota. Sem parênteses, *p.nota é erro.
  • Variável struct → ponto; ponteiro para struct → seta; turma[i] já é struct → ponto.
  • Para uma função mudar a struct, passe o endereço: void f(struct Aluno *a) e f(&ana).
  • Para só ler sem copiar, use const struct Aluno *: o compilador barra qualquer mudança.
  • Structs no heap: malloc(sizeof *p) (com o *!), confira o NULL, termine com free.
  • Uma struct com os dados e funções que recebem um ponteiro para ela organiza o código: a lista que cresce virou adicionar(&lista, valor).