Operador ->, alocação dinâmica com malloc e o padrão construtor/destrutor em C
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 | ✅ |
// 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
// 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!
-> em ProfundidadeO 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;
}
*ptr.campoFuncionario *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
.) 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.
mallocQuando 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.
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
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;
}
#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;
}
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:
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);
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;
}
char *nome), sempre libere os campos internos antes de liberar a struct. Inverter a ordem causa acesso a memória já liberada.
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:
// 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
// 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;
}
Aluno *p = malloc(sizeof(Aluno)); p.nota = 8.5f; // ❌ ERRO: p é ponteiro, não struct — use -> p->nota = 8.5f; // ✅ correto
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
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
/* ❌ 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 |
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;
}
| 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 |
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ê.