Aula 28 — Ponteiros para Structs

Operador ->, alocação dinâmica com malloc e o padrão construtor/destrutor em C

1. Revisão Rápida: Por que Ponteiro para Struct?

Na Aula 27 você aprendeu que structs podem ser passadas por valor ou por ponteiro. Esta aula aprofunda o segundo caso — que é o mais usado em código C real — e acrescenta a alocação dinâmica de structs no heap.

Cenário Mecanismo Custo Modifica original?
Leitura de struct pequena Por valor: void f(Ponto p) Copia todos os bytes ❌
Leitura de struct grande Ponteiro const: void f(const Aluno *p) 8 bytes (o ponteiro) ❌ (protegido por const)
Modificar a struct original Ponteiro: void f(Aluno *p) 8 bytes ✅
Struct criada no heap Aluno *p = malloc(sizeof(Aluno)) Só existe via ponteiro ✅
Java — new sempre aloca no heap
// Em Java, new sempre aloca no heap
// e retorna uma referência:
Aluno a = new Aluno();
a.nome = "Ana";

// Quando 'a' sai de escopo,
// o GC libera automaticamente
C — você escolhe onde alocar
// Na stack (automático):
Aluno a;
strcpy(a.nome, "Ana");

// No heap (manual):
Aluno *p = malloc(sizeof(Aluno));
strcpy(p->nome, "Ana");
free(p); // obrigatório!

2. O Operador -> em Profundidade

O operador seta é simplesmente um atalho para desreferenciar um ponteiro e acessar um campo. Conhecê-lo bem é essencial — ele aparece em todas as funções que trabalham com structs alocadas dinamicamente ou passadas por ponteiro.

ptr->campo é exatamente equivalente a (*ptr).campo. O operador -> foi criado para tornar esse padrão ubíquo mais legível. Sempre que você vir ->, leia mentalmente "acesse o campo X do objeto apontado por este ponteiro".
#include <stdio.h>
#include <string.h>

typedef struct {
    char  nome[40];
    int   idade;
    float salario;
} Funcionario;

int main() {
    Funcionario f = {"Maria Silva", 32, 6500.0f};
    Funcionario *p = &f;

    /* As quatro formas são idênticas */
    printf("Nome (var):        %s\n",  f.nome);
    printf("Nome (ptr seta):   %s\n",  p->nome);
    printf("Nome (deref+ponto):%s\n", (*p).nome);

    /* Modificar via ponteiro */
    p->salario *= 1.10f;    // reajuste de 10% — modifica f diretamente
    p->idade++;

    printf("\nApós modificação via ponteiro:\n");
    printf("  Salário: R$ %.2f\n", f.salario);   // f já reflete a mudança
    printf("  Idade:   %d\n",      f.idade);

    return 0;
}
Nome (var): Maria Silva
Nome (ptr seta): Maria Silva
Nome (deref+ponto):Maria Silva

Após modificação via ponteiro:
  Salário: R$ 7150.00
  Idade: 33

Precedência: cuidado com *ptr.campo

Funcionario *p = &f;

p->salario = 5000.0f;    // ✅ acessa campo salario via ponteiro
(*p).salario = 5000.0f;  // ✅ equivalente

*p.salario = 5000.0f;    // ❌ ERRADO! Equivale a *(p.salario)
                          //    ponto tem precedência maior que *
                          //    e p não é uma struct — é um ponteiro
⚠️ O ponto (.) tem precedência maior que o asterisco (*). Por isso *ptr.campo tenta aplicar o ponto em ptr (que é ponteiro, não struct) — erro de compilação. Use sempre ptr->campo ou (*ptr).campo com parênteses explícitos.

3. Alocando uma Struct no Heap com malloc

Quando a struct precisa sobreviver além da função que a criou, ou quando o número de structs só é conhecido em tempo de execução, a alocação dinâmica é a solução.

Java — new retorna referência
Aluno a = new Aluno();
// JVM aloca no heap,
// retorna referência
a.matricula = 1001;
a.nota = 8.5f;

// GC libera quando não há
// mais referências
C — malloc retorna ponteiro
Aluno *a = malloc(sizeof(Aluno));
if (a == NULL) { /* tratar erro */ }
// você aloca no heap,
// recebe ponteiro
a->matricula = 1001;
a->nota = 8.5f;

free(a); // você libera
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    int   matricula;
    char  nome[50];
    float nota;
} Aluno;

int main() {
    /* Alocar UMA struct no heap */
    Aluno *a = (Aluno *) malloc(sizeof(Aluno));
    if (a == NULL) {
        fprintf(stderr, "Erro: sem memória!\n");
        return 1;
    }

    /* Preencher via operador seta */
    a->matricula = 1001;
    strncpy(a->nome, "Ana Oliveira", sizeof(a->nome) - 1);
    a->nome[sizeof(a->nome) - 1] = '\0';
    a->nota = 8.5f;

    printf("Aluno: %d | %s | %.1f\n",
           a->matricula, a->nome, a->nota);

    /* Liberar — obrigatório! */
    free(a);
    a = NULL;

    return 0;
}
Aluno: 1001 | Ana Oliveira | 8.5

Alocar um array de structs no heap

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

typedef struct {
    char  nome[40];
    float preco;
} Produto;

int main() {
    int n;
    printf("Quantos produtos? ");
    scanf("%d", &n);

    /* Array dinâmico de structs */
    Produto *catalogo = (Produto *) malloc(n * sizeof(Produto));
    if (catalogo == NULL) return 1;

    for (int i = 0; i < n; i++) {
        printf("Nome do produto %d: ", i + 1);
        scanf(" %39[^\n]", catalogo[i].nome);
        printf("Preço: ");
        scanf("%f", &catalogo[i].preco);
    }

    printf("\n--- Catálogo ---\n");
    for (int i = 0; i < n; i++)
        printf("%-20s R$ %.2f\n", catalogo[i].nome, catalogo[i].preco);

    free(catalogo);    // libera tudo com um free só
    return 0;
}
Quantos produtos? 2
Nome do produto 1: Caneta
Preço: 2.50
Nome do produto 2: Caderno
Preço: 12.90

--- Catálogo ---
Caneta R$ 2.50
Caderno R$ 12.90

4. Padrão Construtor / Destrutor em C

C não tem construtores ou destrutores como Java. O padrão equivalente é criar funções explícitas de criação e liberação — por convenção chamadas criar/destruir ou init/free:

Java — construtor e GC
class Aluno {
    String nome;
    float  nota;

    // Construtor:
    Aluno(String nome, float nota) {
        this.nome = nome;
        this.nota = nota;
    }
    // Destrutor: não existe
    // GC cuida da liberação
}

Aluno a = new Aluno("Ana", 8.5f);
C — funções criar/destruir
typedef struct {
    char  nome[50];
    float nota;
} Aluno;

// "Construtor":
Aluno *aluno_criar(const char *nome,
                    float nota);

// "Destrutor":
void aluno_destruir(Aluno *a);

Aluno *a = aluno_criar("Ana", 8.5f);
aluno_destruir(a);
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    char  *nome;     // ponteiro — tamanho dinâmico
    int    idade;
    float  salario;
} Funcionario;

/* "Construtor" — aloca e inicializa */
Funcionario *funcionario_criar(const char *nome, int idade, float salario) {
    Funcionario *f = (Funcionario *) malloc(sizeof(Funcionario));
    if (f == NULL) return NULL;

    f->nome = (char *) malloc(strlen(nome) + 1);
    if (f->nome == NULL) {
        free(f);       // limpa o que já foi alocado antes de retornar NULL
        return NULL;
    }
    strcpy(f->nome, nome);
    f->idade   = idade;
    f->salario = salario;
    return f;
}

/* "Destrutor" — libera na ordem inversa da criação */
void funcionario_destruir(Funcionario *f) {
    if (f == NULL) return;
    free(f->nome);    // libera o nome primeiro (campo interno)
    free(f);          // depois libera a struct em si
}

void funcionario_exibir(const Funcionario *f) {
    printf("%-20s | Idade: %3d | R$ %.2f\n",
           f->nome, f->idade, f->salario);
}

void funcionario_reajuste(Funcionario *f, float pct) {
    f->salario *= (1.0f + pct / 100.0f);
}

int main() {
    Funcionario *f1 = funcionario_criar("Ana Oliveira",  30, 6000.0f);
    Funcionario *f2 = funcionario_criar("Carlos Mota",   45, 9500.0f);
    Funcionario *f3 = funcionario_criar("Beatriz Lima",  28, 5200.0f);

    if (!f1 || !f2 || !f3) {
        fprintf(stderr, "Erro de alocação!\n");
        return 1;
    }

    printf("=== Antes do reajuste ===\n");
    funcionario_exibir(f1);
    funcionario_exibir(f2);
    funcionario_exibir(f3);

    funcionario_reajuste(f1, 10.0f);
    funcionario_reajuste(f2,  8.0f);
    funcionario_reajuste(f3, 12.0f);

    printf("\n=== Após reajuste ===\n");
    funcionario_exibir(f1);
    funcionario_exibir(f2);
    funcionario_exibir(f3);

    funcionario_destruir(f1);
    funcionario_destruir(f2);
    funcionario_destruir(f3);

    return 0;
}
=== Antes do reajuste ===
Ana Oliveira | Idade: 30 | R$ 6000.00
Carlos Mota | Idade: 45 | R$ 9500.00
Beatriz Lima | Idade: 28 | R$ 5200.00

=== Após reajuste ===
Ana Oliveira | Idade: 30 | R$ 6600.00
Carlos Mota | Idade: 45 | R$ 10260.00
Beatriz Lima | Idade: 28 | R$ 5824.00
Ordem de liberação importa: Quando uma struct contém campos que são ponteiros para memória alocada dinamicamente (como char *nome), sempre libere os campos internos antes de liberar a struct. Inverter a ordem causa acesso a memória já liberada.

5. Array Dinâmico de Ponteiros para Struct

Uma abordagem mais flexível é manter um array de ponteiros para structs, em vez de um array de structs diretamente. Isso permite structs de tamanhos variados e facilita reordenação sem copiar dados:

Array de structs direto
// Copia toda a struct ao trocar
Produto catalogo[5];
// Trocar catalogo[0] e [1]:
Produto tmp = catalogo[0];
catalogo[0] = catalogo[1];
catalogo[1] = tmp;
// copia ~50 bytes cada troca
Array de ponteiros para struct
// Troca apenas ponteiros (8 bytes)
Produto *catalogo[5];
// Trocar catalogo[0] e [1]:
Produto *tmp = catalogo[0];
catalogo[0]  = catalogo[1];
catalogo[1]  = tmp;
// copia apenas 8 bytes cada troca
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    char  nome[40];
    float nota;
    int   matricula;
} Aluno;

/* Compara dois alunos por nota (decrescente) */
int comparar(const Aluno *a, const Aluno *b) {
    if (a->nota > b->nota) return -1;
    if (a->nota < b->nota) return  1;
    return 0;
}

int main() {
    int n = 4;

    /* Array dinâmico de ponteiros */
    Aluno **turma = (Aluno **) malloc(n * sizeof(Aluno *));
    if (!turma) return 1;

    /* Aloca cada aluno individualmente */
    const char *nomes[] = {"Ana","Bruno","Carla","Diego"};
    float notas[] = {8.5f, 6.0f, 9.5f, 7.0f};
    int mats[]    = {1001, 1002, 1003, 1004};

    for (int i = 0; i < n; i++) {
        turma[i] = (Aluno *) malloc(sizeof(Aluno));
        if (!turma[i]) return 1;
        strncpy(turma[i]->nome, nomes[i], 39);
        turma[i]->nota       = notas[i];
        turma[i]->matricula  = mats[i];
    }

    /* Ordenar por nota (troca apenas ponteiros) */
    for (int i = 0; i < n - 1; i++)
        for (int j = i + 1; j < n; j++)
            if (comparar(turma[i], turma[j]) > 0) {
                Aluno *tmp = turma[i];
                turma[i]   = turma[j];
                turma[j]   = tmp;
            }

    printf("Ranking por nota:\n");
    for (int i = 0; i < n; i++)
        printf("  %dº  %-10s  %.1f\n",
               i + 1, turma[i]->nome, turma[i]->nota);

    /* Liberar cada aluno e depois o array de ponteiros */
    for (int i = 0; i < n; i++)
        free(turma[i]);
    free(turma);

    return 0;
}
Ranking por nota:
  1º Carla 9.5
  2º Ana 8.5
  3º Diego 7.0
  4º Bruno 6.0
Vantagem do array de ponteiros: Reordenar custa apenas a troca de ponteiros (8 bytes cada), independente do tamanho da struct. Para structs grandes com muitos campos, a diferença de desempenho é significativa. Em Java, arrays de objetos funcionam exatamente assim internamente — você nunca manipula os objetos diretamente, apenas referências.

6. Erros Comuns com Ponteiros para Struct

Erro 1 — Usar ponto em vez de seta com ponteiro

Aluno *p = malloc(sizeof(Aluno));

p.nota = 8.5f;      // ❌ ERRO: p é ponteiro, não struct — use ->
p->nota = 8.5f;     // ✅ correto

Erro 2 — Esquecer de alocar antes de usar

Aluno *p;            // ❌ ponteiro não inicializado
p->nota = 8.5f;     // ❌ segfault! p aponta para lugar inválido

Aluno *p = malloc(sizeof(Aluno));  // ✅ aloca primeiro
if (p) p->nota = 8.5f;            // ✅ usa após verificar

Erro 3 — Não liberar campos dinâmicos antes da struct

typedef struct { char *nome; float nota; } Aluno;

Aluno *a = malloc(sizeof(Aluno));
a->nome  = malloc(50);
strcpy(a->nome, "Ana");

free(a);             // ❌ libera a struct mas vaza a->nome!

free(a->nome);       // ✅ libera o campo interno primeiro
free(a);             // ✅ depois libera a struct

Erro 4 — Retornar ponteiro para struct local

/* ❌ ERRADO — struct local é destruída ao retornar */
Aluno *criarLocal() {
    Aluno a = {1001, "Ana", 8.5f};
    return &a;    // dangling pointer!
}

/* ✅ CORRETO — aloca no heap e retorna o ponteiro */
Aluno *criarHeap() {
    Aluno *p = malloc(sizeof(Aluno));
    if (p) {
        p->matricula = 1001;
        strcpy(p->nome, "Ana");
        p->nota = 8.5f;
    }
    return p;    // ponteiro para heap — persiste após o retorno
}
Erro Sintoma Solução
Usar . em ponteiro Erro de compilação Usar ->
Usar ponteiro sem alocar Segfault imediato Sempre malloc antes de usar
Liberar struct antes dos campos Memory leak dos campos internos Liberar campos primeiro, struct depois
Retornar &variavel_local Comportamento indefinido (dangling pointer) Alocar no heap com malloc

7. Exemplo Integrador — Cadastro com Alocação Dinâmica

Um mini-sistema de cadastro que une todos os conceitos desta aula: alocação dinâmica de struct, campos com ponteiro interno, padrão criar/destruir e array dinâmico de ponteiros:

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

typedef struct {
    int    codigo;
    char  *descricao;   // ponteiro — tamanho exato no heap
    float  preco;
    int    estoque;
} Produto;

/* Cria produto no heap */
Produto *produto_criar(int cod, const char *desc,
                        float preco, int estoque) {
    Produto *p = (Produto *) malloc(sizeof(Produto));
    if (!p) return NULL;

    p->descricao = (char *) malloc(strlen(desc) + 1);
    if (!p->descricao) { free(p); return NULL; }

    p->codigo   = cod;
    strcpy(p->descricao, desc);
    p->preco    = preco;
    p->estoque  = estoque;
    return p;
}

/* Libera produto do heap */
void produto_destruir(Produto *p) {
    if (!p) return;
    free(p->descricao);
    free(p);
}

/* Exibe produto */
void produto_exibir(const Produto *p) {
    printf("  [%04d] %-25s R$%8.2f  Estoque: %d\n",
           p->codigo, p->descricao, p->preco, p->estoque);
}

/* Aplica desconto */
void produto_desconto(Produto *p, float pct) {
    p->preco *= (1.0f - pct / 100.0f);
}

/* Busca por código — retorna ponteiro ou NULL */
Produto *catalogo_buscar(Produto **cat, int n, int codigo) {
    for (int i = 0; i < n; i++)
        if (cat[i]->codigo == codigo)
            return cat[i];
    return NULL;
}

int main() {
    int n = 5;
    Produto **catalogo = (Produto **) malloc(n * sizeof(Produto *));
    if (!catalogo) return 1;

    catalogo[0] = produto_criar(1001, "Notebook Lenovo",   3499.90f, 10);
    catalogo[1] = produto_criar(1002, "Mouse sem fio",        89.90f, 50);
    catalogo[2] = produto_criar(1003, "Teclado mecânico",    299.00f, 25);
    catalogo[3] = produto_criar(1004, "Monitor 24\"",        1199.00f,  8);
    catalogo[4] = produto_criar(1005, "Headset gamer",        249.90f, 30);

    printf("=== Catálogo original ===\n");
    for (int i = 0; i < n; i++)
        produto_exibir(catalogo[i]);

    /* Aplicar 15% de desconto nos itens acima de R$500 */
    for (int i = 0; i < n; i++)
        if (catalogo[i]->preco > 500.0f)
            produto_desconto(catalogo[i], 15.0f);

    printf("\n=== Após desconto de 15%% em itens > R$500 ===\n");
    for (int i = 0; i < n; i++)
        produto_exibir(catalogo[i]);

    /* Busca por código */
    Produto *encontrado = catalogo_buscar(catalogo, n, 1003);
    if (encontrado)
        printf("\nProduto 1003: %s\n", encontrado->descricao);

    /* Liberar tudo */
    for (int i = 0; i < n; i++)
        produto_destruir(catalogo[i]);
    free(catalogo);

    return 0;
}
=== Catálogo original ===
  [1001] Notebook Lenovo R$ 3499.90 Estoque: 10
  [1002] Mouse sem fio R$ 89.90 Estoque: 50
  [1003] Teclado mecânico R$ 299.00 Estoque: 25
  [1004] Monitor 24" R$ 1199.00 Estoque: 8
  [1005] Headset gamer R$ 249.90 Estoque: 30

=== Após desconto de 15% em itens > R$500 ===
  [1001] Notebook Lenovo R$ 2974.92 Estoque: 10
  [1002] Mouse sem fio R$ 89.90 Estoque: 50
  [1003] Teclado mecânico R$ 299.00 Estoque: 25
  [1004] Monitor 24" R$ 1019.15 Estoque: 8
  [1005] Headset gamer R$ 249.90 Estoque: 30

Produto 1003: Teclado mecânico

8. Comparação Geral — C vs. Java

Aspecto C Java
Criar objeto no heap Tipo *p = malloc(sizeof(Tipo)); Tipo p = new Tipo();
Acessar campo via referência p->campo (operador seta) p.campo (sempre ponto)
Liberar memória Manual — free(p); Automático — Garbage Collector
Campo nulo / não inicializado ⚠️ Contém lixo — precisa inicializar explicitamente ✅ Inicializado com 0/null automaticamente
Campos com alocação interna Deve liberar campos antes da struct (free(p->nome); free(p);) GC cuida de toda a cadeia de referências
Verificar referência nula if (p == NULL) if (p == null)
Padrão construtor Função: Tipo *tipo_criar(...) Construtor da classe: new Tipo(...)
Padrão destrutor Função: void tipo_destruir(Tipo *p) Não existe — GC gerencia
Resumo mental para quem vem do Java: malloc(sizeof(Tipo)) é o equivalente de new Tipo(), e free(p) é o que o GC faz automaticamente em Java. O operador -> é o que Java usa internamente quando você escreve obj.campo — a diferença é que Java esconde essa indireção de você.

Quiz — Aula 28

1. Qual é a saída?

#include <stdio.h>
#include <stdlib.h>
typedef struct { int x; int y; } Ponto;
int main() {
    Ponto *p = malloc(sizeof(Ponto));
    p->x = 3;
    p->y = 4;
    printf("%d %d", p->x, p->y);
    free(p);
}

2. Por que *p.campo gera erro de compilação quando p é um ponteiro para struct?

3. Em Java, new Tipo() aloca no heap e o GC libera automaticamente. O equivalente mais próximo em C é:

4. Uma struct tem char *nome como campo (alocado com malloc). Na hora de liberar, qual é a ordem correta?

5. Qual a vantagem de usar um array de ponteiros para struct em vez de um array de structs diretamente?

6. Qual é o erro no código abaixo?

typedef struct { int x; } S;
S *criar() {
    S local = {42};
    return &local;
}

7. No padrão construtor/destrutor de C, quando uma função tipo_destruir recebe NULL, ela deve:

8. Em Java, obj.campo sempre acessa via referência implícita. Em C, quando você escreve p->campo: