Vetores e o Primeiro Contato com a Memória Contígua

[344] Vetores e o Primeiro Contato com a Memória Contígua

Guardados lado a lado na memória — e é esse detalhe, não a sintaxe dos colchetes, que faz do vetor a ponte direta para os ponteiros. Índices que começam em zero, o cast que salva a média da divisão inteira, o array decay que faz o sizeof mentir dentro da função, e a ausência total de verificação de limites.
Linguagem C

8 min de leitura

Até agora, cada dado morava em sua própria variável. Mas e se você precisa guardar as notas de trinta alunos, ou as temperaturas de um ano inteiro? Declarar nota1, nota2, ..., nota30 seria absurdo. Para isso existem os vetores (ou arrays): uma sequência de valores do mesmo tipo, guardados lado a lado na memória. E é justamente esse "lado a lado" que torna este artigo tão importante — ele é a ponte direta para entender ponteiros, o tema do próximo capítulo.

Declarando e acessando um vetor

Um vetor é declarado com o tipo, o nome e, entre colchetes, o número de elementos:

#include <stdio.h>

int main(void) {
    int notas[5]; // reserva espaço para 5 inteiros

    notas[0] = 8;
    notas[1] = 6;
    notas[2] = 9;
    notas[3] = 7;
    notas[4] = 10;

    printf("Primeira nota: %d\n", notas[0]);
    printf("Última nota: %d\n", notas[4]);
    return 0;
}

O detalhe que confunde todo iniciante: os índices começam em zero. Um vetor de 5 elementos tem índices de 0 a 4 — nunca 5. O primeiro elemento é notas[0]; o último é notas[4]. Guarde isso com força, porque o erro de acessar notas[5] é um dos mais comuns e perigosos do C.

Inicializando de uma vez

Você pode preencher o vetor na própria declaração, com chaves:

#include <stdio.h>

int main(void) {
    int notas[5] = {8, 6, 9, 7, 10};

    // Se você omitir o tamanho, o compilador conta para você:
    double precos[] = {1.50, 2.75, 0.99}; // tamanho 3, automático

    printf("%d %.2f\n", notas[2], precos[1]); // 9 2.75
    return 0;
}

Se você fornecer menos valores que o tamanho declarado, o resto é preenchido com zeros — um truque útil: int zeros[100] = {0}; cria cem zeros.

Vetores e laços: feitos um para o outro

A verdadeira força dos vetores aparece quando os combinamos com laços. Aquela contagem "de 0 a n−1" que praticamos no artigo anterior existe exatamente por causa disto:

#include <stdio.h>

int main(void) {
    int notas[5] = {8, 6, 9, 7, 10};
    int soma = 0;

    for (int i = 0; i < 5; i++) {
        soma += notas[i];
    }

    double media = (double) soma / 5;
    printf("Soma: %d, Média: %.2f\n", soma, media);
    return 0;
}

Repare no (double) soma — isso é um cast, uma conversão de tipo explícita. Sem ele, soma / 5 seria uma divisão inteira (resultado 8, perdendo as casas decimais). Ao converter soma para double antes, forçamos a divisão a ser de ponto flutuante. É outra daquelas sutilezas do C que o compilador não avisa.

O calcanhar de Aquiles: o C não vigia as fronteiras

Aqui está a característica mais perigosa dos vetores em C, e preciso que você a leve a sério. O C não verifica se o índice é válido. Se o vetor tem 5 elementos e você acessa a posição 10, o C não reclama — ele simplesmente lê ou escreve no pedaço de memória que estiver ali, seja lá o que for:

int v[5] = {1, 2, 3, 4, 5};
v[10] = 99;   // desastre silencioso: escreve fora do vetor
printf("%d\n", v[7]); // lê lixo, ou trava o programa

Isso é o que se chama de comportamento indefinido (undefined behavior). O programa pode continuar como se nada fosse, pode imprimir lixo, pode travar na hora, ou — pior — travar horas depois, num ponto totalmente diferente do código. É a fonte número um de bugs e vulnerabilidades em C. A responsabilidade de respeitar os limites é inteiramente sua. Lembre-se: o C confia em você, e essa confiança tem um preço.

Um aviso antes de testar isto online: um playground roda seu código uma vez só, num compilador e num sistema só — e o resultado parece estável, que é justamente a impressão errada. Para ver o problema ser detectado em vez de mascarado, use o Compiler Explorer com a flag -fsanitize=address: o acesso fora de faixa passa a interromper o programa com um relatório apontando a linha. É a ferramenta que a Fase 6 do curso aprofunda.

O tamanho de um vetor com sizeof

Como o C não guarda o tamanho junto do vetor, um truque comum é calculá-lo com sizeof: o tamanho total dividido pelo tamanho de um elemento:

#include <stdio.h>

int main(void) {
    int v[] = {10, 20, 30, 40, 50, 60};
    int tamanho = sizeof(v) / sizeof(v[0]);

    printf("O vetor tem %d elementos.\n", tamanho); // 6

    for (int i = 0; i < tamanho; i++) {
        printf("%d ", v[i]);
    }
    printf("\n");
    return 0;
}

Atenção a uma pegadinha importante: esse truque só funciona no mesmo escopo em que o vetor foi declarado. Quando você passa um vetor para uma função, ele "vira" um ponteiro e perde a informação de tamanho — e aí o sizeof mente. Esse fenômeno, o array decay, é a razão pela qual precisamos passar o tamanho como argumento separado. É também a maior pista de que vetor e ponteiro são, no fundo, quase a mesma coisa.

Passando vetores para funções

Por causa desse decaimento, a convenção em C é sempre passar o vetor e o seu tamanho:

#include <stdio.h>

int maior_elemento(int v[], int n) {
    int maior = v[0];
    for (int i = 1; i < n; i++) {
        if (v[i] > maior) {
            maior = v[i];
        }
    }
    return maior;
}

int main(void) {
    int numeros[] = {23, 8, 42, 15, 4, 16};
    int n = sizeof(numeros) / sizeof(numeros[0]);

    printf("Maior elemento: %d\n", maior_elemento(numeros, n));
    return 0;
}

Saída: Maior elemento: 42

E aqui aparece algo curioso, que só entenderemos de verdade no próximo artigo: ao contrário das variáveis simples (que são copiadas), o vetor não é copiado ao ser passado. A função recebe acesso ao vetor original e poderia modificá-lo. Isso quebra a regra de "passagem por valor" que aprendemos? Não — na verdade, ela a confirma, porque o que é copiado é o endereço do vetor, não seu conteúdo. Segure essa ideia: ela é a chave dos ponteiros.

Vetores de duas dimensões

Vetores podem ter mais de uma dimensão — úteis para matrizes, tabuleiros e imagens:

#include <stdio.h>

int main(void) {
    int matriz[2][3] = {
        {1, 2, 3},
        {4, 5, 6}
    };

    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%d ", matriz[i][j]);
        }
        printf("\n");
    }
    return 0;
}

matriz[i][j] acessa a linha i, coluna j. Por baixo dos panos, mesmo uma matriz é armazenada como uma sequência linear e contígua na memória — o C apenas calcula a posição correta para você.

O que vem a seguir

Hoje demos nosso primeiro passo dentro da memória: aprendemos que um vetor é uma fileira de valores dispostos lado a lado, e vimos pistas de que ele guarda uma relação profunda com endereços de memória. No próximo artigo, vamos encarar de frente o conceito mais poderoso — e mais temido — do C: os ponteiros. Você vai descobrir o que é um endereço de memória, como guardá-lo numa variável, e por que quase tudo que fizemos até aqui estava, silenciosamente, preparando você para este momento.

Fontes e leituras recomendadas

Exercícios

Exercício 1

Declare um vetor com os números {5, 12, 8, 130, 44} e escreva um laço que imprima cada elemento com seu índice, no formato [0] = 5.

Ver resposta

✓ Resposta:

#include <stdio.h>
int main(void) {
    int v[] = {5, 12, 8, 130, 44};
    int n = sizeof(v) / sizeof(v[0]);
    for (int i = 0; i < n; i++) {
        printf("[%d] = %d\n", i, v[i]);
    }
    return 0;
}

Exercício 2

Escreva uma função double media(double v[], int n) que receba um vetor de double e seu tamanho e devolva a média dos elementos.

Ver resposta

✓ Resposta: A média é a soma dividida pela quantidade; como v já é double, não há problema de divisão inteira, mas convém garantir n > 0:

double media(double v[], int n) {
    double soma = 0.0;
    for (int i = 0; i < n; i++) {
        soma += v[i];
    }
    return (n > 0) ? soma / n : 0.0;
}

Exercício 3

Escreva uma função int contar_maiores(int v[], int n, int limite) que conte quantos elementos do vetor são estritamente maiores que limite.

Ver resposta

✓ Resposta:

int contar_maiores(int v[], int n, int limite) {
    int qtd = 0;
    for (int i = 0; i < n; i++) {
        if (v[i] > limite) {
            qtd++;
        }
    }
    return qtd;
}

Exercício 4

Compile este trecho com gcc -fsanitize=address e rode: int v[3] = {1,2,3}; printf("%d\n", v[5]);. O que o AddressSanitizer relata? Por que isso não apareceria sem a ferramenta?

Ver resposta

✓ Resposta: O AddressSanitizer relata um erro do tipo "stack-buffer-overflow" (ou global-buffer-overflow), apontando a leitura de v[5] num vetor de 3 posições, com a linha exata. Sem a ferramenta, o acesso a v[5] é comportamento indefinido: o C simplesmente lê o que estiver naquele endereço e segue em frente, muitas vezes sem travar — por isso o bug passa despercebido. O sanitizer instrumenta a memória para flagrar o acesso inválido no momento em que ele ocorre.

Exercício 5

Crie uma matriz 3×3 de inteiros, preencha-a com uma tabuada (m[i][j] = (i+1)*(j+1)) usando laços aninhados e imprima-a em formato de grade.

Ver resposta

✓ Resposta:

#include <stdio.h>
int main(void) {
    int m[3][3];
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            m[i][j] = (i + 1) * (j + 1);
        }
    }
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%2d ", m[i][j]); // %2d alinha em 2 colunas
        }
        printf("\n");
    }
    return 0;
}

Saída:

1  2  3
2  4  6
3  6  9
Comentários

Mais em Linguagem C

Ponteiros para Função: Passando Comportamento como Argumento
Ponteiros para Função: Passando Comportamento como Argumento

O nome de uma função, escrito sem os parênteses, é um endereço — e dessa…

Ponteiro para Ponteiro e Argumentos de Linha de Comando
Ponteiro para Ponteiro e Argumentos de Linha de Comando

Cada asterisco a mais é um passo a mais na cadeia — e são dois deles que o…

Ponteiros: O que É um Endereço de Memória
Ponteiros: O que É um Endereço de Memória

A memória como uma rua de casas numeradas, e o ponteiro como a variável que…