Pular para conteúdo

Semana 11 — Ponteiros (parte 2) e passagem por referência

Nesta semana

Unidade 3 · Seg 19/10 · Qua 21/10 · Sex 23/10

Na Semana 10 você conheceu os ponteiros: variáveis que guardam endereços de memória. Vimos o operador & (que devolve o endereço de uma variável) e o operador * (que "olha para dentro" do endereço, a desreferência). Foi a base. Nesta semana colocamos essa base para trabalhar de verdade — e resolvemos, finalmente, um mistério que carregamos desde a Semana 01: por que o scanf sempre exigiu o &?

O clímax da semana é a passagem por referência. Você vai entender como, num mundo em que o C passa tudo por valor (Semana 04), conseguimos ainda assim escrever uma função que altera a variável de quem a chamou e que devolve vários resultados de uma vez. O truque — você já suspeita — são os ponteiros. Digite e execute cada exemplo: este é um tópico que só "cai a ficha" com as mãos no teclado.

🎯 Objetivos de aprendizagem

Ao final desta semana, você deverá ser capaz de:

  • recapitular com segurança os operadores & e * e a declaração int *p (Semana 10);
  • explicar a relação entre ponteiros e vetores, incluindo a equivalência v[i] == *(v + i), e por que o nome de um vetor "vale" o endereço do seu primeiro elemento;
  • explicar, finalmente, por que alterar um vetor dentro de uma função afeta o original (fechando a dúvida deixada na Semana 07);
  • usar aritmética de ponteiros para percorrer um vetor, entendendo que p + 1 avança um elemento (deslocamento escalado pelo sizeof do tipo);
  • implementar a passagem por referência: passar &x e receber int *p, alterando a variável original via *p — em contraste explícito com a passagem por valor da Semana 04;
  • escrever a função canônica void troca(int *a, int *b) e funções que devolvem vários resultados por ponteiros (como divide(..., int *q, int *r));
  • explicar por que o scanf usa & e usar const int *p para parâmetros de só leitura.

📖 Segunda — Estudo do conteúdo

1. Recapitulando a Semana 10

Antes de avançar, vamos firmar o terreno. Um ponteiro é uma variável cujo valor é um endereço de memória — ou seja, ele "aponta para" onde outra variável mora. Três peças formam o alfabeto dos ponteiros:

Notação Nome O que faz
int *p; declaração cria um ponteiro p que aponta para um int
&x operador endereço-de devolve o endereço da variável x
*p operador desreferência acessa o conteúdo guardado no endereço que p aponta

Um exemplo mínimo que junta os três:

recap.c
#include <stdio.h>

int main(void) {
    int x = 42;
    int *p = &x;     // p recebe o ENDERECO de x (p "aponta para" x)

    printf("x  = %d\n", x);      // 42  (o valor de x)
    printf("&x = %p\n", (void*) &x);  // o endereco de x (algo como 0x7ffd...)
    printf("p  = %p\n", (void*) p);   // o MESMO endereco (p guarda &x)
    printf("*p = %d\n", *p);     // 42  (*p desreferencia: o valor NO endereco)

    *p = 100;        // altera o CONTEUDO apontado por p...
    printf("x  = %d\n", x);      // 100  (... ou seja, altera o proprio x!)
    return 0;
}

A última parte é a semente de tudo que veremos hoje: escrever em *p é escrever em x, porque p aponta para x. Guarde essa ideia — ela é o coração da passagem por referência.

A leitura correta de int *p

Leia int *p de trás para frente: "*p é um int". Ou seja, quando eu desreferenciar p, obtenho um int — logo, p é um ponteiro para int. Essa leitura evita a confusão entre o * da declaração (que diz "isto é um ponteiro") e o * da desreferência (que diz "acesse o conteúdo").

Se algum desses pontos ainda parece nebuloso, releia a Semana 10 antes de continuar — o resto da semana depende dele.

2. Ponteiros e vetores: dois lados da mesma moeda

Aqui mora uma das ideias mais elegantes (e, no começo, mais confusas) do C. Considere:

int v[5] = {10, 20, 30, 40, 50};

Em C, o nome de um vetor, quando usado numa expressão, "vale" o endereço do seu primeiro elemento. Isto é:

v  ==  &v[0]     // o nome do vetor equivale ao endereco do elemento 0

Os 5 inteiros ficam lado a lado na memória (dizemos que o vetor é contíguo). Se v[0] está no endereço, digamos, 1000, e cada int ocupa 4 bytes, então:

flowchart LR
    subgraph mem["Memória (vetor contíguo)"]
    direction LR
    E0["v[0]<br/>10<br/>end. 1000"] --- E1["v[1]<br/>20<br/>end. 1004"] --- E2["v[2]<br/>30<br/>end. 1008"] --- E3["v[3]<br/>40<br/>end. 1012"] --- E4["v[4]<br/>50<br/>end. 1016"]
    end
    P["v (nome do vetor)<br/>vale 1000 = &v[0]"] --> E0

Como o nome v é um endereço, podemos aplicar a desreferência e a aritmética de ponteiros (próxima seção) para chegar em qualquer elemento. A equivalência fundamental é:

v[i]  ==  *(v + i)

Ou seja, a notação de colchetes v[i] é, literalmente, açúcar sintático para *(v + i): "pegue o endereço do primeiro elemento, ande i posições, e desreferencie". Vamos ver isso funcionando:

vetor_ponteiro.c
#include <stdio.h>

int main(void) {
    int v[5] = {10, 20, 30, 40, 50};

    printf("%d\n", v[2]);       // 30  (notacao normal)
    printf("%d\n", *(v + 2));   // 30  (a MESMA coisa, com ponteiros)

    printf("%p\n", (void*) v);      // endereco do primeiro elemento...
    printf("%p\n", (void*) &v[0]);  // ... identico a &v[0]

    return 0;
}

Curiosidade 💡 — os colchetes são simétricos

Como v[i] é apenas *(v + i), e a soma é comutativa (v + i == i + v), o C aceita, literalmente, escrever i[v] — e isso compila e funciona! 3[v] é o mesmo que v[3]. Ninguém escreve assim na vida real (é ilegível), mas o fato de ser válido revela que os colchetes são, no fundo, pura aritmética de ponteiros disfarçada.

2.1 Fechando a dúvida da Semana 07

Na Semana 07 você aprendeu vetores e notou algo curioso: quando uma função altera um vetor, a mudança persiste fora dela. Isso contrariava diretamente a regra da Semana 04 ("função recebe cópia, não altera o original"). Ficou a pergunta no ar. Agora temos a resposta.

Quando você "passa um vetor" para uma função, o C não copia o vetor inteiro. Como o nome do vetor vale o endereço do primeiro elemento, o que é copiado é apenas esse endereço. A função recebe, portanto, um ponteiro para o mesmo vetor da main — e, ao mexer em v[i], mexe no vetor original.

dobra_vetor.c
#include <stdio.h>

// Estes dois cabecalhos sao EQUIVALENTES em C:
//   void dobra(int v[], int n)
//   void dobra(int *v, int n)      <- o C trata "int v[]" como "int *v"
void dobra(int v[], int n) {
    for (int i = 0; i < n; i++) {
        v[i] = v[i] * 2;   // altera o vetor ORIGINAL (v aponta para ele)
    }
}

int main(void) {
    int nums[3] = {1, 2, 3};
    dobra(nums, 3);        // passa o ENDERECO do vetor (nao uma copia)
    for (int i = 0; i < 3; i++)
        printf("%d ", nums[i]);   // 2 4 6  -> mudou de verdade!
    printf("\n");
    return 0;
}

Vetor não é exceção à passagem por valor

Muita gente memoriza "vetor passa por referência, escalar passa por valor". Isso é uma meia-verdade perigosa. O C sempre passa por valor. O que acontece é que, no caso do vetor, o valor copiado é um endereço. A cópia é do endereço, não dos dados — e é por isso que a função enxerga (e altera) o vetor original. Entender isto é entender ponteiros; decorar a "regra" é ficar preso na superfície.

3. Aritmética de ponteiros

Ponteiros suportam um tipo especial de aritmética. A regra de ouro:

p + 1 não avança 1 byte — avança para o PRÓXIMO elemento do tipo apontado.

O compilador escala automaticamente o deslocamento pelo sizeof do tipo. Se p aponta para um int (4 bytes) no endereço 1000, então p + 1 vale 1004, p + 2 vale 1008, e assim por diante. Você raciocina em elementos; o C cuida dos bytes.

flowchart LR
    subgraph m["Aritmética de ponteiros (int = 4 bytes)"]
    direction LR
    A["*p<br/>end. 1000"] --- B["*(p+1)<br/>end. 1004"] --- C["*(p+2)<br/>end. 1008"] --- D["*(p+3)<br/>end. 1012"]
    end

Com isso, dá para percorrer um vetor sem índice nenhum, usando só o ponteiro:

percorre.c
#include <stdio.h>

int main(void) {
    int v[5] = {10, 20, 30, 40, 50};
    int *p = v;          // p aponta para v[0] (lembre: v == &v[0])

    // Forma 1: caminhando com aritmetica sobre p
    for (int i = 0; i < 5; i++) {
        printf("%d ", *(p + i));   // 10 20 30 40 50
    }
    printf("\n");

    // Forma 2: incrementando o proprio ponteiro
    for (p = v; p < v + 5; p++) {  // p++ avanca um int por vez
        printf("%d ", *p);          // 10 20 30 40 50
    }
    printf("\n");
    return 0;
}

Na Forma 2, p++ faz p "pular" para o próximo elemento, e v + 5 é o endereço logo após o último — um limite natural para o laço.

Curiosidade 💡 — a aritmética escala pelo sizeof

Por que p + 1 avança 4 bytes para um int, mas avançaria 8 para um double e 1 para um char? Porque o C multiplica o "1" pelo sizeof do tipo apontado. É como uma régua cujas marcas têm o tamanho do elemento: para int, cada passo vale 4 bytes; para double, 8. Isso é o que torna v[i] (isto é, *(v + i)) sempre correto, independentemente do tipo — você nunca precisa multiplicar por sizeof na mão.

Não some ponteiros de tipos diferentes (nem invente endereços)

Aritmética de ponteiros só faz sentido dentro de um mesmo vetor (ou logo após o último elemento, como limite). Acessar *(v + 5) num vetor de 5 posições, ou somar ponteiros de tipos incompatíveis, é comportamento indefinido: pode "funcionar" hoje e corromper a memória amanhã. A régua só é confiável dentro do vetor.

4. Passagem por referência — o clímax da semana

Reencontremos a limitação da Semana 04. Lá vimos que o C passa argumentos por valor: a função recebe uma cópia, e mexer nela não afeta a original. O exemplo clássico:

void tenta_dobrar(int x) {
    x = x * 2;          // altera apenas a COPIA local
}
// na main: n = 10; tenta_dobrar(n);  -> n continua 10

A pergunta que ficou em aberto (na caixa !!! question da Semana 04) foi: "e se eu precisar que a função altere a variável original?". A resposta é a passagem por referência, e ela tem três passos:

  1. Em vez do valor da variável, passe o seu endereço: funcao(&x).
  2. A função recebe esse endereço num parâmetro ponteiro: void funcao(int *p).
  3. Dentro da função, use a desreferência para alcançar e alterar a original: *p = ....

Assim, a função não recebe uma cópia do valor de x — recebe uma cópia do endereço de x. E, com o endereço em mãos, ela chega até a caixa original e escreve nela.

agora_dobra.c
#include <stdio.h>

void dobra_de_verdade(int *p) {
    *p = *p * 2;         // escreve no CONTEUDO apontado -> altera a original
}

int main(void) {
    int n = 10;
    dobra_de_verdade(&n);       // passa o ENDERECO de n
    printf("%d\n", n);          // 20  -> mudou!
    return 0;
}

O contraste com a Semana 04 é total. Vale colocar lado a lado:

Passagem por valor (Sem. 04) Passagem por referência (Sem. 11)
Parâmetro int x int *p
Chamada f(n) f(&n)
O que é copiado o valor de n o endereço de n
Dentro da função mexe na cópia mexe no original (via *p)
Original muda? não sim

Erro clássico: querer alterar sem usar ponteiro

Escrever void dobra(int x){ x = x*2; } e esperar que a variável de fora mude é o erro número um deste tópico. Compila sem reclamar, mas não faz nada de útil para o chamador. Se a função precisa alterar o original, o parâmetro tem que ser ponteiro (int *) e a chamada tem que passar o endereço (&). Não há meio-termo.

Curiosidade 💡 — por que o scanf sempre exigiu o &

Este é o mistério da Semana 01, finalmente resolvido! O scanf precisa guardar o valor lido na sua variável — ou seja, precisa alterar uma variável do chamador. Isso é exatamente passagem por referência. Por isso você escreve scanf("%d", &idade): você entrega ao scanf o endereço de idade, e ele, lá dentro, faz o equivalente a *p = valor_lido;. O printf, ao contrário, só os valores (não precisa mudar nada), e por isso não usa &: printf("%d", idade). A regra que você decorou há dez semanas era, o tempo todo, um caso de ponteiros.

5. O exemplo canônico: void troca(int *a, int *b)

Trocar o conteúdo de duas variáveis é impossível com passagem por valor (a função só mexeria em cópias). Com ponteiros, é direto — e este é o "Olá, mundo" da passagem por referência:

troca.c
#include <stdio.h>

void troca(int *a, int *b) {
    int temp = *a;   // guarda o conteudo apontado por a
    *a = *b;         // no lugar de *a, poe o conteudo de *b
    *b = temp;       // no lugar de *b, poe o valor guardado
}

int main(void) {
    int x = 5, y = 9;
    printf("antes:  x = %d, y = %d\n", x, y);   // x = 5, y = 9
    troca(&x, &y);                               // passa os ENDERECOS
    printf("depois: x = %d, y = %d\n", x, y);   // x = 9, y = 5
    return 0;
}

Vale seguir o passo a passo com x = 5 e y = 9. Na chamada troca(&x, &y), o parâmetro a passa a apontar para x, e b para y:

flowchart LR
    subgraph main["main"]
    X["x = 5"]
    Y["y = 9"]
    end
    subgraph troca["troca"]
    A["a &rarr;"] --> X
    B["b &rarr;"] --> Y
    end
  1. int temp = *a;temp recebe *a, ou seja 5.
  2. *a = *b; → o conteúdo apontado por a (isto é, x) vira *b, ou seja 9. Agora x = 9.
  3. *b = temp; → o conteúdo apontado por b (isto é, y) vira temp, ou seja 5. Agora y = 5.

Ao voltar para a main, x e y estão trocados de verdade, porque troca operou sobre os endereços reais das duas variáveis.

A versão errada que parece certa

Compare com esta versão quebrada:

void troca_errada(int a, int b) {   // por VALOR: recebe copias
    int temp = a; a = b; b = temp;  // troca as copias locais...
}                                    // ... e as joga fora ao retornar
Chamando troca_errada(x, y), nada muda em x e y — a função trocou duas cópias que morrem no fim. É um ótimo experimento para a sexta-feira: rode as duas e compare.

6. Devolvendo vários resultados por ponteiros

O return devolve um só valor. Mas e quando o problema pede dois ou mais resultados de uma vez? A passagem por referência resolve: cada resultado sai por um ponteiro-parâmetro.

O exemplo perfeito é a divisão inteira, que produz quociente e resto ao mesmo tempo:

divide.c
#include <stdio.h>

// Nao usa return: entrega os resultados escrevendo em *quociente e *resto.
void divide(int a, int b, int *quociente, int *resto) {
    *quociente = a / b;   // parte inteira da divisao
    *resto     = a % b;   // resto da divisao
}

int main(void) {
    int q, r;
    divide(17, 5, &q, &r);   // passa &q e &r para a funcao preencher
    printf("17 / 5 = %d, resto %d\n", q, r);   // 3, resto 2
    return 0;
}

Repare no padrão: a e b entram por valor (são só dados de entrada), enquanto q e r entram por referência (são saídas que a função deve preencher). Misturar os dois estilos na mesma função é comum e perfeitamente idiomático em C.

Entrada por valor, saída por ponteiro

Uma convenção útil ao ler a assinatura de uma função C: parâmetros "normais" (int a) costumam ser entradas; parâmetros ponteiros (int *r) costumam ser saídas (ou dados grandes que não vale a pena copiar, como vetores). Isso é só uma convenção, não uma regra da linguagem — mas ajuda muito a entender funções da biblioteca padrão.

7. Protegendo o que é apontado: const

Se um parâmetro ponteiro serve só para leitura (a função não deve alterar o que ele aponta), é boa prática declará-lo com const. Isso documenta a intenção e faz o compilador impedir uma escrita acidental:

const_ptr.c
#include <stdio.h>

// "const int *v": a funcao promete NAO alterar o conteudo de v.
int soma_vetor(const int *v, int n) {
    int soma = 0;
    for (int i = 0; i < n; i++) {
        soma += v[i];   // leitura: OK
        // v[i] = 0;    // se descomentar, o compilador ACUSA ERRO (bom!)
    }
    return soma;
}

int main(void) {
    int nums[4] = {1, 2, 3, 4};
    printf("%d\n", soma_vetor(nums, 4));   // 10
    return 0;
}

Você verá const int * o tempo todo na biblioteca padrão — por exemplo, funções que recebem uma string apenas para lê-la a declaram como const char *. É um contrato: "pode me entregar seus dados; prometo que não vou mexer neles".

Curiosidade 💡 — ponteiros para ponteiros (int **)

Se um ponteiro pode apontar para um int, nada impede que um ponteiro aponte para outro ponteiro. Um int **pp é um "ponteiro para ponteiro para int". Parece abstrato, mas é exatamente o que permite uma função alterar um ponteiro de quem a chamou (a mesma ideia de troca, um nível acima) — e é a base das matrizes e dos vetores de strings (char **argv, que você já viu de relance na main). Vamos abrir essa caixa na Semana 13. Por ora, saiba que a ideia se repete: acrescentar um * sobe um nível de indireção.

8. Erros comuns desta semana (resumão)

Checklist de armadilhas

  • Querer alterar a variável do chamador com passagem por valor (int x) em vez de ponteiro (int *p).
  • Esquecer o & na chamada: troca(x, y) em vez de troca(&x, &y).
  • Confundir o * da declaração (int *p) com o * da desreferência (*p = 5).
  • Achar que p + 1 avança 1 byte (avança 1 elemento, escalado pelo sizeof).
  • Acessar além do vetor com aritmética (*(v + n) num vetor de n posições).
  • Desreferenciar um ponteiro não inicializado (aponta para "lixo") — trava certa.
  • Esquecer que int v[] como parâmetro é int *v (não se copia o vetor).
  • Tentar escrever em um const int * (o compilador acusa — e isso é bom).

👥 Quarta — Encontro

Chegue ao encontro tendo estudado o material acima e tendo executado os exemplos — especialmente o troca.c. O encontro é para consolidar e destravar dúvidas, não para ver o conteúdo pela primeira vez. Roteiro previsto:

  1. Revisão relâmpago: & e *, a equivalência v[i] == *(v + i), e o mantra "para alterar o original, passe o endereço".
  2. Visualização passo a passo da passagem por referência, desenhando as "caixas" da memória e as setas dos ponteiros (retomando a ideia da pilha de chamadas da Semana 04).
  3. Galeria de erros: provocamos de propósito o erro de esquecer o & e o de usar int x no lugar de int *p, para você aprender a reconhecer o sintoma (a variável "não muda").

Problema-guia do encontro — normalizar um intervalo: ler dois inteiros a e b e garantir que fiquem em ordem crescente (a <= b); se estiverem trocados, corrigir no lugar, usando a função troca. Depois, ler um vetor e imprimir, de uma só chamada, o maior e o menor — via ponteiros.

encontro.c
#include <stdio.h>

void troca(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// Devolve DOIS resultados (maior e menor) por referencia.
void maior_e_menor(const int *v, int n, int *maior, int *menor) {
    *maior = v[0];
    *menor = v[0];
    for (int i = 1; i < n; i++) {
        if (v[i] > *maior) *maior = v[i];
        if (v[i] < *menor) *menor = v[i];
    }
}

int main(void) {
    int a = 9, b = 5;
    if (a > b) troca(&a, &b);          // garante a <= b, corrigindo no lugar
    printf("ordenados: a = %d, b = %d\n", a, b);   // a = 5, b = 9

    int v[6] = {7, 2, 9, 4, 9, 1};
    int mx, mn;
    maior_e_menor(v, 6, &mx, &mn);     // uma chamada, dois resultados
    printf("maior = %d, menor = %d\n", mx, mn);    // maior = 9, menor = 1
    return 0;
}

Perguntas para pensar antes do encontro: por que maior_e_menor usa const em v, mas não em maior e menor? O que aconteceria se trocássemos troca(&a, &b) por troca(a, b)?


✍️ Sexta — Estudo dirigido

Reserve este tempo para praticar de verdade. Diferentemente das semanas de laços e condicionais, a passagem por referência é pouco testada isoladamente no Beecrowd — ela costuma aparecer "embutida" em problemas maiores. Por isso, mais do que resolver problemas novos, o objetivo desta sexta é revisitar exercícios que você já fez e refatorá-los para usar ponteiros e funções que devolvem resultados por referência.

Exercícios guiados (Beecrowd)

Problema Como aplicar ponteiros/referência
1116 Dividindo Valores escreva void divide(int a, int b, int *q, int *r)
1187 Faixa de Números preencha um vetor via função (parâmetro int v[])
1173 Preenchimento de Vetor I função que preenche o vetor do chamador
1478 Máximo e Mínimo devolva maior e menor por dois ponteiros

(Poucos e propositalmente familiares: a meta é reescrever com o novo ferramental, não vencer a dificuldade do enunciado.)

Desafios para escrever do zero

  1. void troca(int *a, int *b) — implemente e teste com vários pares. Depois escreva a versão errada troca(int a, int b) e comprove que ela não muda x e y.
  2. void maior_e_menor(const int *v, int n, int *maior, int *menor) — devolve os dois extremos de um vetor numa só chamada.
  3. void divide(int a, int b, int *quociente, int *resto) — quociente e resto de uma só vez. Teste com 17 / 5, 20 / 4, 7 / 10.
  4. Refatoração por referência: pegue uma função sua que hoje usa return (por exemplo, double area_retangulo(...)) e reescreva-a como void area_retangulo(double b, double h, double *area), "devolvendo" o resultado pelo ponteiro. Compare os dois estilos: qual fica mais legível? (Dica: para um resultado, return costuma ganhar; ponteiros brilham quando são vários.)
  5. Percurso com ponteiro: escreva uma função que imprime um vetor sem usar índice — apenas com um ponteiro p e aritmética (for (p = v; p < v + n; p++)).

Para investigar (curiosidade prática)

  • Imprima e compare os quatro valores a seguir para um vetor int v[3]: v, &v[0], v + 1, &v[1]. O que você conclui sobre v versus &v[0]? E sobre a diferença, em bytes, entre v e v + 1? (Use %p e o cast (void*).)
  • Confirme na prática que troca(&x, &y) muda x e y, enquanto a versão troca(int a, int b) não muda. Explique, com suas palavras, por quê — usando as palavras "cópia", "endereço" e "desreferência".

✅ Checklist de autoavaliação

Marque com sinceridade — se algum item não estiver ✅, volte à seção correspondente.

  • Explico os operadores & e * e sei ler int *p como "*p é um int".
  • Entendo que o nome de um vetor vale &v[0] e que v[i] == *(v + i).
  • Sei explicar por que uma função altera o vetor original (recebe um ponteiro).
  • Sei que p + 1 avança um elemento (escalado pelo sizeof) e percorro um vetor com aritmética de ponteiros.
  • Implemento passagem por referência: f(&x) na chamada, int *p no parâmetro, *p = ... para alterar o original.
  • Escrevo void troca(int *a, int *b) de cabeça e sigo seu passo a passo.
  • Sei devolver vários resultados por ponteiros (divide(..., int *q, int *r)).
  • Explico por que o scanf usa & e uso const int * para parâmetros de só leitura.

🔗 Referências e para se aprofundar

  • Material 2026.1 — Aula 11: Ponteiros e Alocação Dinâmica.
  • BACKES, André. Linguagem C: completa e descomplicada (ponteiros, ponteiros e vetores, passagem de parâmetros por referência).
  • KERNIGHAN, B.; RITCHIE, D. The C Programming Language (o clássico "K&R"), cap. 5 (Pointers and Arrays) — a referência definitiva sobre a relação ponteiro/vetor.
  • Referência: cppreference — pointer (detalhes de aritmética de ponteiros e conversões).
  • Prática: Beecrowd.

Antes de seguir para a Semana 12

Você acaba de dominar a peça que faltava: com ponteiros, uma função pode alterar o mundo à sua volta, não só devolver um número. Mas todos os endereços que manipulamos até aqui pertenciam a variáveis que o compilador criou para nós — na pilha, com tamanho fixo, decidido em tempo de compilação. E se precisarmos de um vetor cujo tamanho só conhecemos durante a execução? Para isso, teremos que pedir memória à máquina nós mesmos — a alocação dinâmica (malloc, free), o tema da Semana 12. É onde os ponteiros deixam de ser um recurso a mais e passam a ser indispensáveis.