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
-O0e-O2.
"Parece funcionar" é o perigoso, porque significa que o bug vai para produção. Três defesas:
- Percorra com
i < n. A maioria dos estouros são erros de deslocamento por um na condição do laço. - Valide índices que vêm da entrada.
- Compile com um sanitizer durante o desenvolvimento.
gcc -fsanitize=address -g prog.ctransforma 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.