Menu

Arrays em C: declarar, inicializar e percorrer

Como declarar e inicializar arrays em C, indexar a partir do zero, calcular o tamanho com sizeof, percorrer os elementos e por que ler além do fim é comportamento indefinido em vez de uma mensagem de erro.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Um array é um bloco de tamanho fixo de elementos do mesmo tipo, dispostos um após o outro na memória. Esse arranjo contíguo é toda a história: é por isso que a indexação é instantânea, por que arrays e ponteiros são tão intimamente relacionados, e por que C pode te entregar o endereço do elemento zero e deixar você encontrar o resto por aritmética.

Declarando um array

Tipo do elemento, nome e um tamanho entre colchetes:

int    scores[5];       // 5 ints
double prices[100];     // 100 doubles
char   initials[3];     // 3 chars

O tamanho é o número de elementos, e precisa ser conhecido no ponto em que o array é declarado - normalmente um literal ou uma constante criada com #define. A memória é reservada imediatamente; nada é alocado depois.

Um array local recém-declarado contém lixo, não zeros:

A segunda linha pode imprimir zeros, pode imprimir números enormes e pode variar entre execuções ou entre uma compilação de depuração e uma de release. Ler esses valores é comportamento indefinido; inicialize antes de ler.

Inicializando

Várias formas, todas úteis:

O idioma {0} é o que vale lembrar: qualquer elemento que você não listar é inicializado com zero, então um único zero zera o array inteiro. Inicializadores designados ([4] = 99) deixam você definir posições específicas e manter o resto em zero, o que é prático para tabelas de consulta que são quase todas vazias.

Uma coisa que você não pode fazer é atribuir um array depois do fato:

int a[3] = {1, 2, 3};
int b[3];
b = a;                    // erro: um array não é atribuível
memcpy(b, a, sizeof a);   // é assim que se copia um

A indexação começa em zero

O primeiro elemento é arr[0] e o último é arr[n-1]. Não existe arr[n].

Execute esse último laço e olhe os endereços: cada um está exatamente sizeof(int) bytes depois do anterior. Os elementos realmente ficam dispostos um atrás do outro:

 índice     0         1         2         3         4
         +--------+--------+--------+--------+--------+
 valor   |   88   |   95   |   75   |   60   |  100   |
         +--------+--------+--------+--------+--------+
 desloc.  +0       +4       +8       +12      +16      bytes
          ^
          &scores[0], que é também o que `scores` vira ao decair

A indexação baseada em zero não é uma escolha arbitrária - ela faz do índice um deslocamento a partir do início, que é exatamente do que a aritmética de endereços precisa. scores[i] é definido como *(scores + i); veja ponteiros e arrays.

Descobrindo o tamanho

C não guarda o tamanho de um array em lugar nenhum em tempo de execução, mas o compilador o conhece, e sizeof te dá acesso:

sizeof(arr) / sizeof(arr[0]) é o idioma. Dividir por arr[0] em vez de por um sizeof(double) fixo significa que a linha continua funcionando se você mudar o tipo do elemento.

A armadilha: isso só funciona onde o array foi declarado. Passe o array para uma função e o parâmetro é um ponteiro, então sizeof mede o ponteiro - tipicamente 8 bytes, dando um "tamanho" de 1 ou 2. É por isso que toda função em C que recebe um array também recebe uma contagem:

Percorrendo um array

O formato padrão é um laço for de 0 enquanto i < n:

Note i < n, não i <= n. Com n elementos os índices válidos vão de 0 a n - 1, então <= executa uma passagem a mais e lê além do fim. Iniciar hottest e coldest a partir de temps[0] em vez de 0 é o outro hábito que vale copiar - começar um máximo em zero quebra silenciosamente com dados todos negativos.

Fora dos limites é comportamento indefinido

Esta é a parte de C que surpreende quem vem de outras linguagens. Não há verificação de limites. Nenhuma em tempo de compilação, nenhuma em tempo de execução. arr[10] em um array de cinco elementos não é um erro - é um cálculo de endereço que o compilador realiza sem comentar nada.

int arr[5] = {1, 2, 3, 4, 5};
arr[7] = 99;     // escreve 8 bytes além do fim do array
int x = arr[-1]; // lê antes do início

O que acontece a seguir é comportamento indefinido, e os sintomas são inutilmente variados:

  • parece funcionar, porque a memória atingida não estava em uso;
  • outra variável muda sem razão visível;
  • o programa trava com uma falha de segmentação, possivelmente muito depois;
  • o comportamento difere entre -O0 e -O2.

"Parece funcionar" é o perigoso, porque significa que o bug vai para produção. Três defesas:

  1. Percorra com i < n. A maioria dos estouros são erros de deslocamento por um na condição do laço.
  2. Valide índices que vêm da entrada.
  1. Compile com um sanitizer durante o desenvolvimento. gcc -fsanitize=address -g prog.c transforma a maioria dos estouros em um relatório imediato nomeando o arquivo, a linha e o array envolvido.

Arrays de outros tipos

A mesma sintaxe funciona para qualquer tipo de elemento, incluindo structs:

A linha char word[6] = "hello"; merece uma pausa: uma string em C é só um array de char cujo último byte significativo é '\0'. Esse terminador é o motivo de o array precisar de seis posições para cinco letras.

Arrays de tamanho variável, brevemente

C99 permite um array cujo tamanho é um valor de tempo de execução:

int n = get_count();
int buffer[n];        // um array de tamanho variável

Dois cuidados. O tamanho fica fixo assim que o array é criado, então um VLA não é uma lista que cresce. E a memória vem da pilha, então um n grande ou controlado por um atacante pode estourá-la e derrubar o programa - que é por que muitos projetos, incluindo o kernel Linux, proíbem VLAs por completo. Eles também são opcionais para implementações C11 suportarem.

Quando o tamanho é genuinamente desconhecido até a execução, aloque em vez disso:

Note que arr[i] se lê de forma idêntica quer arr seja um array ou um ponteiro para memória alocada - a sintaxe de indexação não se importa. Detalhes completos em memória dinâmica.

Perguntas frequentes

Como se declara um array em C?

Informe o tipo do elemento, um nome e um tamanho entre colchetes: int scores[5]; reserva espaço para cinco ints. O tamanho precisa ser uma constante conhecida no ponto em que o array é declarado (fora dos arrays de tamanho variável), e os elementos começam não inicializados a menos que você forneça valores.

Como descobrir o tamanho de um array em C?

sizeof(arr) / sizeof(arr[0]) - o tamanho total em bytes dividido pelo tamanho de um elemento. Isso só funciona no escopo onde o array foi declarado: depois que o array é passado para uma função ele decaiu para um ponteiro e sizeof mede o ponteiro em vez do array.

O que acontece se você passar do fim de um array em C?

Nada te impede. C não faz nenhuma verificação de limites, então arr[10] em um array de 5 elementos lê ou escreve na memória que estiver ali. Isso é comportamento indefinido: pode imprimir lixo, corromper outra variável ou travar - e pode parecer funcionar até o dia em que não funciona.

Como inicializar todos os elementos de um array com zero em C?

int arr[100] = {0}; - qualquer elemento que você não listar é inicializado com zero, então um único zero zera o array inteiro. int arr[100] = {}; também funciona em C23. Sem nenhum inicializador, o conteúdo de um array local é lixo indeterminado.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR