Professor: Gabriel Soares Baptista
A aula 11 mostrou como modelar dados com struct.
Usamos símbolos como &, *, -> e NULL na prática.
Mas ainda faltava desmontar a lógica por trás deles.
Também notamos que o vetor Aluno turma[100] tem tamanho fixo.
Esta aula resolve as duas coisas: o que são ponteiros por dentro e como criar dados dinamicamente.
&, * e -> para localizar e modificar dados. malloc, calloc, realloc e free. Quando declaramos uma variavel, o programa reserva um espaco na memoria.
Esse espaco tem uma posicao concreta com um endereco.
int idade = 20;
printf("Valor: %d\n", idade); // 20
printf("Endereco: %p\n", (void*)&idade); // 0x...
idade representa o valor da variavel. &idade representa o endereco da variavel. int idade = 20;
int *p = &idade;
Leia assim:
idade guarda o valor 20&idade produz o endereco de idadep guarda esse enderecop aponta para idadep nao guarda 20. Quem guarda 20 e idade. p guarda o endereco de idade.
int *p; // ponteiro para int
float *q; // ponteiro para float
Aluno *a; // ponteiro para Aluno
char *texto; // ponteiro para char
O tipo informa o que esperamos encontrar no endereco e quantos bytes ler ou escrever.
* tambem acessa o conteudo apontadoO * aparece em dois contextos:
int *p)*p)printf("Endereco: %p\n", (void*)p); // o endereco
printf("Conteudo: %d\n", *p); // o valor la encontrado
p e endereco. *p e o conteudo encontrado nesse endereco.
*p = 35; // altera a variavel apontada
printf("%d\n", idade); // agora imprime 35
p e *p nao sao a mesma coisa. p e endereco. *p e o conteudo.
printf() e scanf() na memoriaint idade = 20;
printf("Valor: %d\n", idade); // le o conteudo
printf("Endereco: %p\n", &idade); // mostra a localizacao
int idade;
scanf("%d", &idade); // precisa do endereco para escrever
scanf() nao foi chamado para observar a variavel. Ele precisa escrever nela. Para escrever, precisa saber onde ela esta.
scanfscanf("%d", &idade); // CORRETO: entrega o endereco
scanf("%d", idade); // ERRADO: idade e um valor, nao um endereco
O fluxo:
scanf interpreta esse valor&idade entrega o endereco corretovoid tentar_alterar(int x){
x = 100;
}
int main(void){
int n = 10;
tentar_alterar(n);
printf("%d\n", n); // imprime 10
return 0;
}
A funcao recebeu uma copia do valor de n.
Alterar x nao altera n, porque sao variaveis em enderecos diferentes.
void alterar(int *p){
*p = 100;
}
int main(void){
int n = 10;
alterar(&n);
printf("%d\n", n); // imprime 100
return 0;
}
&n entrega o endereco de n. A funcao usa *p para acessar e modificar a variavel original.
Foi exatamente isso que aplicar_bonus(Aluno *a, float bonus) fez na aula 11.
Passagem por valor:
main: n = 10 ---copia---> funcao: x = 10
Passagem por endereco:
funcao: p = 0x100 ---mesmo endereco---> main: n em 0x100
Na passagem por valor, sao duas variaveis separadas.
Na passagem por endereco, a funcao trabalha com a mesma posicao de memoria.
struct e ->Aluno aluno;
aluno.matricula = 1001; // acesso direto: .
Aluno *a = &aluno;
a->matricula = 1001; // acesso via ponteiro: ->
. quando temos a estrutura diretamente. -> quando temos um ponteiro para a estrutura. a->faltas equivale a (*a).faltas. Na pratica, usamos -> porque a leitura fica mais natural.
NULL = ausencia de endereco validoint *p = NULL;
p existe como variavel, mas nao aponta para nenhum objeto valido.
Aluno *a = buscar_aluno_por_matricula(...);
if(a != NULL){
aplicar_bonus(a, bonus);
}
Se a matricula existe: a recebe o endereco do aluno.
Se nao existe: a recebe NULL. O teste protege contra acesso invalido.
Se a == NULL, nao faz sentido tentar a->faltas. Nao ha estrutura valida naquele endereco.
int v[3] = {10, 20, 30};
printf("%d\n", v[0]); // 10
printf("%d\n", *v); // 10 tambem
O nome do vetor se comporta como o endereco do primeiro elemento.
O vetor ocupa posicoes consecutivas de memoria: 0x300, 0x304, 0x308...
Isso explica &turma[i] em ler_aluno(&turma[i]): turma[i] e o aluno na posicao i, e &turma[i] e o endereco desse aluno.
Aluno turma[100];
n = 3. n > 100, o programa quebra ou corrompe memoria. int n;
scanf("%d", &n);
Aluno turma[n]; // VLA: funciona em alguns compiladores, mas nao e portatil
Far sentido o programa ocupar memoria para 100 alunos se a maioria das execucoes usa apenas 3 ou 4?
Memoria do programa
/ \
Pilha (stack) Heap (monte)
Automatica Manual
int x, float v[10] malloc, free
Tamanho fixo Tamanho em execucao
Liberada ao sair Liberada com free
Ate agora usamos so a pilha. A alocacao dinamica usa o heap.
malloc(): pedindo memoria#include <stdlib.h>
void *malloc(size_t tamanho);
Recebe a quantidade de bytes e devolve o endereco do bloco (ou NULL se falhar).
int *p = malloc(sizeof(int));
*p = 42;
sizeof(int) calcula os bytes de um int (normalmente 4). malloc pede esses bytes ao heap. p guarda o endereco devolvido. *p normalmente. int n;
scanf("%d", &n);
int *v = malloc(n * sizeof(int));
Agora n veio da entrada. O heap aloca exatamente o necessario.
Apos alocar, usamos v[i] como um vetor comum:
for(i = 0; i < n; i++)
v[i] = i * 10;
malloc(n) aloca n bytes. Para n inteiros, precisa ser malloc(n * sizeof(int)).
int *v = malloc(n * sizeof(int));
if(v == NULL){
printf("Erro: memoria insuficiente.\n");
return 1;
}
malloc pode falhar se o heap nao tiver memoria suficiente.
Ignorar a verificacao significa arriscar acessar *v quando v == NULL, o que causa falha de segmentacao.
int n;
scanf("%d", &n);
Aluno *turma = malloc(n * sizeof(Aluno));
if(turma == NULL){
printf("Erro ao alocar turma.\n");
return 1;
}
for(i = 0; i < n; i++)
ler_aluno(&turma[i]);
| Antes (fixo) | Depois (dinamico) |
|---|---|
Aluno turma[100] |
Aluno *turma = malloc(n * sizeof(Aluno)) |
| Sempre 100 posicoes | Exatamente n posicoes |
n maximo 100 |
Limitado pela RAM |
As funcoes da biblioteca nao mudam -- recebem Aluno * e tanto faz se veio da pilha ou do heap.
calloc(): alocando e zerandomalloc entrega lixo de memoria. calloc entrega tudo zerado.
int *v1 = malloc(5 * sizeof(int)); // ?, ?, ?, ?, ?
int *v2 = calloc(5, sizeof(int)); // 0, 0, 0, 0, 0
| Funcao | Argumentos | Inicializacao |
|---|---|---|
malloc(n * sizeof(int)) |
Total de bytes | Nao limpa |
calloc(n, sizeof(int)) |
Quantidade, tamanho | Zera tudo |
calloc e mais seguro para vetores usados como acumuladores.
realloc(): redimensionandoint *v = malloc(3 * sizeof(int));
v[0] = 10; v[1] = 20; v[2] = 30;
int *temp = realloc(v, 5 * sizeof(int));
if(temp != NULL){
v = temp;
v[3] = 40;
v[4] = 50;
}
temp) para nao perder o bloco original se falhar. v = realloc(v, ...) e perigoso! Se realloc falhar, retorna NULL e voce perde o endereco original.
void turma_adicionar(Aluno **turma, int *n, Aluno aluno){
Aluno *temp = realloc(*turma, (*n + 1) * sizeof(Aluno));
if(temp == NULL) return;
*turma = temp;
(*turma)[*n] = aluno;
(*n)++;
}
Aluno **turma: para modificar o ponteiro da main, passamos o endereco dele.
Mesma logica de scanf("%d", &idade): para alterar um Aluno *, passamos Aluno **.
free(): devolvendo memoriaint *v = malloc(100 * sizeof(int));
// usa o vetor...
free(v);
v = NULL; // pratica defensiva
Depois de free(v), o bloco volta ao sistema.
Acessar v[i] depois disso e comportamento indefinido.
free(NULL) e seguro, entao v = NULL apos free protege contra uso acidental.
for(i = 0; i < 1000; i++){
int *v = malloc(1000000 * sizeof(int));
// esqueceu de chamar free(v)
}
A cada iteracao, o endereco anterior e perdido. Milhoes de bytes nunca serao liberados.
Em programas que rodam horas (servidores, editores), vazamentos se acumulam e degradam o sistema.
Cada malloc, calloc ou realloc bem-sucedido exige um free correspondente.
int main(void){
int n;
scanf("%d", &n);
Aluno *turma = malloc(n * sizeof(Aluno));
if(turma == NULL) return 1;
for(i = 0; i < n; i++)
ler_aluno(&turma[i]);
// aplica bonus, imprime relatorio...
free(turma);
return 0;
}
Mudou muito pouco em relacao a versao com vetor fixo.
A diferenca: o tamanho e exato, pode ser qualquer valor e liberamos no final.
| Aula | Problema central | Ferramenta |
|---|---|---|
| 11. TADs | Dados soltos, sem modelo | struct, typedef, enum |
| 12. Ponteiros e Aloc. Dinamica | Localizar, criar, redimensionar | &, *, ->, malloc, free |
struct modela as entidades.
Ponteiros localizam e modificam essas entidades na memoria.
Alocacao dinamica cria e redimensiona esses dados durante a execucao.
Escreva uma funcao void trocar(int *a, int *b) que troca os valores entre duas variavies.
No main, declare x = 10 e y = 20, chame trocar(&x, &y) e imprima os valores depois da troca.
Saida esperada:
x = 20, y = 10
Leia um inteiro n, aloque um vetor de float com malloc, preencha com notas lidas do teclado, calcule a media e imprima.
Nao esqueca de verificar se malloc retornou NULL e de liberar a memoria no final.
Exemplo de execucao:
3
8.5 7.0 9.5
Media: 8.33
Comece com um vetor de 3 inteiros alocado com malloc e preenchido com [10, 20, 30].
Use realloc para expandir para 5 posicoes e preencher as novas com 40 e 50.
Imprima todos os elementos no final.
Saida esperada:
10 20 30 40 50
Agora sabemos localizar dados com ponteiros e criar e redimensionar dados com alocacao dinamica.
Com struct, ponteiros e alocacao dinamica, temos as ferramentas para construir programas que se adaptam ao tamanho real dos dados.
Na proxima aula, vamos estudar recursao e continuar com exercicios praticos para consolidar a leitura e a escrita de codigo.