C Arrays
A Ana dá aula para uma turma de 5 alunos e quer guardar as notas. Com o que você viu até aqui, ela criaria nota1, nota2, nota3, nota4 e nota5. E se a turma tiver 40 alunos? Quarenta variáveis, quarenta printf, e nenhum laço consegue passar por elas, porque cada uma tem um nome diferente. O array (em inglês, "arranjo", "fileira"; em português também se diz vetor) resolve isso: uma variável que guarda vários valores do mesmo tipo, um ao lado do outro, cada um com um número de posição.
- Como criar um array (
int notas[5]) e por que a primeira posição é a 0. - Como ler e trocar um elemento pelo índice (
notas[2]). - Como percorrer o array com um
for: mostrar tudo, somar, tirar a média, achar o maior e procurar um valor. - As formas de dar valores iniciais, incluindo o
= {}do C23, e o que acontece quando você não dá nenhum. - Como escolher o tamanho: número, constante ou um tamanho lido do teclado (VLA).
- Por que
b = aea == bnão fazem o que parece. - O erro mais perigoso: o índice que passa do fim, e por que o compilador quase nunca avisa.
Todos os programas foram compilados com o gcc 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/c, com o comando padrão do guia: gcc -std=c23 -Wall -Wextra arquivo.c -o programa. Antes desta aula, é bom ter visto o laço for: arrays e for andam sempre juntos.
A ideia: a fileira de armários numerados
Pense nos armários de um vestiário: uma fileira de portinhas iguais, todas do mesmo tamanho, coladas uma na outra. A fileira tem um nome ("armários da turma B") e cada portinha tem um número. Para guardar ou pegar alguma coisa, você não precisa de um nome novo para cada armário: diz "armário 3 da turma B".
Um array é exatamente isso:
- a fileira inteira tem um nome:
notas; - todas as portinhas guardam o mesmo tipo de coisa:
int, por exemplo; - cada portinha tem um número de posição, o índice, escrito entre colchetes:
notas[3]; - o número de portinhas é decidido quando a fileira é construída e não muda depois.
Só tem uma surpresa: em C, a contagem das portinhas começa no 0, não no 1. Já já você vê o porquê.
1. O primeiro array
#include <stdio.h>
int main(void) {
int notas[5] = {7, 8, 6, 9, 10};
printf("Primeira nota: %d\n", notas[0]);
printf("Terceira nota: %d\n", notas[2]);
printf("Última nota: %d\n", notas[4]);
return 0;
}
- int
- O tipo de cada elemento. Todos os 5 valores são
int. Um array não mistura tipos. - notas
- O nome do array, escolhido por você com as mesmas regras de qualquer variável (aula Nomes de Variáveis). Costuma ir no plural:
notas,precos,alunos. - [5]
- Quantos elementos o array tem: 5 portinhas. Na criação, o número entre colchetes é o tamanho.
- = {7, 8, 6, 9, 10}
- Os valores iniciais, entre chaves, separados por vírgula, na ordem das posições. Isso se chama inicializar o array.
- notas[0]
- Depois de criado, o número entre colchetes muda de papel: passa a ser o índice, a posição que você quer.
notas[0]é o primeiro elemento, que vale 7. - notas[4]
- O último. Num array de 5, as posições vão de 0 a 4.
$ gcc -std=c23 -Wall -Wextra notas.c -o notas $ ./notas Primeira nota: 7 Terceira nota: 6 Última nota: 10
Na criação (int notas[5]), o número diz quantos elementos existem. Em todo o resto do programa (notas[4]), o número diz qual elemento você quer. Por isso int notas[5] e notas[5] parecem iguais, mas o segundo é um erro: pede a sexta portinha de uma fileira de cinco (seção 9).
2. Por que o índice começa em 0
O índice não é "o número do armário" no sentido de contagem humana. Ele é a distância desde o começo da fileira: o primeiro elemento está a 0 passos do começo, o segundo a 1 passo, e assim por diante. Na memória, os 5 inteiros ficam colados, cada um com 4 bytes (aula Tamanho dos Tipos):
| Índice | [0] | [1] | [2] | [3] | [4] |
|---|---|---|---|---|---|
| Valor | 7 | 8 | 6 | 9 | 10 |
| Distância do começo | 0 bytes | 4 bytes | 8 bytes | 12 bytes | 16 bytes |
| Para a Ana | aluno 1 | aluno 2 | aluno 3 | aluno 4 | aluno 5 |
Para achar notas[3], o computador faz uma conta só: "começo da fileira + 3 × 4 bytes". É por isso que acessar qualquer posição é igualmente rápido, seja a primeira ou a milésima. (Essa conta, com ponteiros, é o assunto de Ponteiros e Arrays, no Nível 2.)
A consequência que você vai usar o tempo todo: num array de N elementos, os índices vão de 0 a N−1. O último elemento de um array de 5 é o [4]; o de um array de 40 é o [39].
Missão: complete o segundo printf para mostrar a nota do segundo aluno, Segunda nota: 8. Qual é o índice do segundo elemento?
#include <stdio.h>
int main(void) {
int notas[5] = {7, 8, 6, 9, 10};
printf("Primeira nota: %d\n", notas[0]);
printf("Segunda nota: %d\n", 0); // troque o 0 pelo elemento certo
return 0;
}
#include <stdio.h>
int main(void) {
int notas[5] = {7, 8, 6, 9, 10};
printf("Primeira nota: %d\n", notas[0]);
printf("Segunda nota: %d\n", notas[1]);
return 0;
}
3. Trocar um elemento
Cada elemento funciona como uma variável comum: dá para dar um valor novo com = e usar em contas.
#include <stdio.h>
int main(void) {
int notas[5] = {7, 8, 6, 9, 10};
printf("Antes: notas[2] = %d\n", notas[2]);
notas[2] = 10;
notas[0] = notas[0] + 1;
printf("Depois: notas[2] = %d\n", notas[2]);
printf("E notas[0] = %d\n", notas[0]);
return 0;
}
- notas[2] = 10;
- O terceiro aluno fez a prova de recuperação: a portinha 2 recebe 10. O 6 que estava lá some.
- notas[0] = notas[0] + 1;
- Um ponto extra para o primeiro aluno: lê o valor da portinha 0 (7), soma 1 e guarda de volta. Também podia ser
notas[0]++;ounotas[0] += 1;(aula ++ e --).
$ gcc -std=c23 -Wall -Wextra trocar.c -o trocar $ ./trocar Antes: notas[2] = 6 Depois: notas[2] = 10 E notas[0] = 8
O índice pode ser uma variável
Entre os colchetes pode ir qualquer conta que dê um número inteiro: notas[i], notas[i + 1], notas[total - 1]. É isso que deixa o array poderoso: um laço pode fazer i valer 0, 1, 2, 3, 4, e notas[i] passa por todos os elementos. O índice tem de ser inteiro: com um double i = 2;, o gcc recusa notas[i] com error: array subscript is not an integer ("o índice do array não é um inteiro"; subscript é o nome técnico do índice).
4. Percorrer o array com for
O for (int i = 0; i < 5; i++) da aula for dá exatamente os índices de um array de 5: 0, 1, 2, 3 e 4. Com ele, a Ana mostra todas as notas e calcula a média:
#include <stdio.h>
int main(void) {
int notas[5] = {7, 8, 6, 9, 10};
int soma = 0;
for (int i = 0; i < 5; i++) {
printf("Aluno %d: nota %d\n", i + 1, notas[i]);
soma = soma + notas[i];
}
printf("Soma: %d\n", soma);
printf("Média: %.1f\n", soma / 5.0);
return 0;
}
- int soma = 0;
- O acumulador começa em zero, como no somatório da aula for.
- for (int i = 0; i < 5; i++)
- Começa no 0 e usa
<: passa por 0, 1, 2, 3, 4 e para antes do 5. É a forma padrão de percorrer um array em C. - i + 1
- Para a Ana, o aluno
[0]é o "aluno 1". Na tela, mostre a contagem humana; no código, use o índice. - soma = soma + notas[i];
- A cada volta, soma o elemento da posição
i. - soma / 5.0
- Dividir por
5.0(e não por5) faz a conta com casas decimais (aula Operadores Aritméticos).
$ gcc -std=c23 -Wall -Wextra percorre.c -o percorre $ ./percorre Aluno 1: nota 7 Aluno 2: nota 8 Aluno 3: nota 6 Aluno 4: nota 9 Aluno 5: nota 10 Soma: 40 Média: 8.0
Se a turma crescer para 40 alunos, o laço continua com 3 linhas. É para isso que o array existe.
Missão: a Ana quer saber quantos alunos passaram (nota 7 ou mais). Dentro do for, some 1 em aprovados quando notas[i] for maior ou igual a 7. O resultado tem de ser Aprovados: 3.
#include <stdio.h>
int main(void) {
int notas[5] = {7, 4, 9, 6, 8};
int aprovados = 0;
for (int i = 0; i < 5; i++) {
// se a nota for 7 ou mais, conte mais um aprovado
}
printf("Aprovados: %d\n", aprovados);
return 0;
}
#include <stdio.h>
int main(void) {
int notas[5] = {7, 4, 9, 6, 8};
int aprovados = 0;
for (int i = 0; i < 5; i++) {
if (notas[i] >= 7) {
aprovados++;
}
}
printf("Aprovados: %d\n", aprovados);
return 0;
}
Encher o array pelo teclado
O scanf (aula Entrada com scanf) guarda direto numa posição. Como em qualquer variável int, ele precisa do &, agora na frente do elemento:
#include <stdio.h>
int main(void) {
int notas[3];
for (int i = 0; i < 3; i++) {
printf("Nota do aluno %d: ", i + 1);
if (scanf("%d", ¬as[i]) != 1) {
return 1;
}
}
printf("Você digitou: %d, %d e %d\n", notas[0], notas[1], notas[2]);
return 0;
}
- int notas[3];
- Três portinhas, ainda sem valor: o
scanfvai enchê-las. - ¬as[i]
- "O endereço do elemento
i": o lugar onde oscanfdeve escrever (aula Memória e Endereços). Os colchetes são resolvidos primeiro: é o endereço denotas[i], e não denotas. - != 1
- Se o
scanfnão conseguiu ler um número (a pessoa digitou letras, ou a entrada acabou), o programa para em vez de continuar com uma nota sem valor.
$ gcc -std=c23 -Wall -Wextra ler.c -o ler $ ./ler Nota do aluno 1: 8 « a Ana digita 8 e Enter Nota do aluno 2: 7 Nota do aluno 3: 10 Você digitou: 8, 7 e 10
5. Três receitas que todo programa usa
Quase tudo que se faz com um array é uma variação de três laços: acumular (a soma, que você já viu), achar o maior e procurar.
Achar o maior
A Ana anotou os pontos de 6 rodadas de um jogo e quer o recorde:
#include <stdio.h>
int main(void) {
int pontos[6] = {12, 45, 7, 45, 30, 18};
int maior = pontos[0];
int posicao = 0;
for (int i = 1; i < 6; i++) {
if (pontos[i] > maior) {
maior = pontos[i];
posicao = i;
}
}
printf("Maior pontuação: %d (rodada %d)\n", maior, posicao + 1);
return 0;
}
- int maior = pontos[0];
- O "campeão provisório" é o primeiro elemento. Não comece com
maior = 0: se todos os valores fossem negativos (temperaturas no inverno da Sibéria), o 0 ganharia sem estar no array. - for (int i = 1; …)
- Começa no 1, porque o elemento 0 já é o campeão: não precisa competir com ele mesmo.
- if (pontos[i] > maior)
- Achou alguém maior? Ele vira o novo campeão, e
posicaoguarda onde ele estava. - > e não >=
- O 45 aparece duas vezes (rodadas 2 e 4). Com
>, o empate não troca o campeão, e fica a primeira vez. Com>=, ficaria a última.
$ gcc -std=c23 -Wall -Wextra maior.c -o maior $ ./maior Maior pontuação: 45 (rodada 2)
Para o menor, é o mesmo laço com < no if.
Procurar um valor
Na lojinha da Ana, cada produto tem um código. O código 330 está cadastrado? Em que posição?
#include <stdio.h>
int main(void) {
int codigos[5] = {101, 205, 330, 412, 587};
int procurado = 330;
int achou = -1;
for (int i = 0; i < 5; i++) {
if (codigos[i] == procurado) {
achou = i;
break;
}
}
if (achou >= 0) {
printf("Código %d está na posição %d\n", procurado, achou);
} else {
printf("Código %d não existe\n", procurado);
}
return 0;
}
- int achou = -1;
- −1 não é um índice válido (os índices começam em 0). Por isso ele serve de sinal: "ainda não achei".
- break;
- Achou: não precisa olhar o resto. O
breaksai do laço na hora (aula break e continue). - if (achou >= 0)
- Depois do laço, se
achoudeixou de ser −1, o valor foi encontrado.
$ gcc -std=c23 -Wall -Wextra procura.c -o procura $ ./procura Código 330 está na posição 2
Essa é a busca linear: olhar um por um, do começo ao fim. Para arrays pequenos é perfeita. Para arrays enormes e em ordem, existe a busca binária (bsearch, aula Ordenar e Buscar).
Arrays de outros tipos
Tudo isso vale para qualquer tipo: double para preços, char para letras, bool para "sim ou não".
#include <stdio.h>
int main(void) {
double precos[4] = {4.50, 12.90, 3.25, 8.00};
char tamanhos[3] = {'P', 'M', 'G'};
double total = 0.0;
for (int i = 0; i < 4; i++) {
total = total + precos[i];
}
printf("Total da compra: R$ %.2f\n", total);
printf("Tamanhos: %c %c %c\n", tamanhos[0], tamanhos[1], tamanhos[2]);
return 0;
}
$ gcc -std=c23 -Wall -Wextra compras.c -o compras $ ./compras Total da compra: R$ 28.65 Tamanhos: P M G
Um array de char com uma marca de fim é o que o C chama de string, o texto. Ele tem aula própria, logo depois desta e da próxima: Strings.
6. As formas de dar valores iniciais
#include <stdio.h>
int main(void) {
int a[5] = {1, 2, 3, 4, 5};
int b[] = {1, 2, 3};
int c[5] = {1, 2};
int d[5] = {};
int e[5] = {[1] = 50, [3] = 70};
printf("a:");
for (int i = 0; i < 5; i++) printf(" %d", a[i]);
printf("\nb:");
for (int i = 0; i < 3; i++) printf(" %d", b[i]);
printf("\nc:");
for (int i = 0; i < 5; i++) printf(" %d", c[i]);
printf("\nd:");
for (int i = 0; i < 5; i++) printf(" %d", d[i]);
printf("\ne:");
for (int i = 0; i < 5; i++) printf(" %d", e[i]);
printf("\n");
return 0;
}
$ gcc -std=c23 -Wall -Wextra formas.c -o formas $ ./formas a: 1 2 3 4 5 b: 1 2 3 c: 1 2 0 0 0 d: 0 0 0 0 0 e: 0 50 0 70 0
- int a[5] = {1, 2, 3, 4, 5};
- Tamanho e todos os valores. A forma completa.
- int b[] = {1, 2, 3};
- Colchetes vazios: o compilador conta os valores e cria um array de 3. Bom quando a lista é fixa: acrescente um valor e o tamanho se ajusta sozinho.
- int c[5] = {1, 2};
- Menos valores que o tamanho: os que faltam viram zero. Sempre, garantido pelo padrão.
- int d[5] = {};
- Chaves vazias: tudo zero. C23 Antes do C23, o jeito era
= {0}(o primeiro é 0 de propósito e os outros viram 0 pela regra de cima), que você vai ver em muito código e continua valendo. - int e[5] = {[1] = 50, [3] = 70};
- Inicializador designado C99: você diz a posição de cada valor. Os outros viram zero. Útil em tabelas grandes com poucos valores.
Experimente: mude os inicializadores e rode. Tente int v[6] = {5}; (só o primeiro é 5?), {[5] = 1} (o último) e int v[] = {9, 8, 7, 6, 5, 4};. Repare que o laço usa 6: se o array mudar de tamanho, ajuste o laço também (a seção 7 mostra como não esquecer).
#include <stdio.h>
int main(void) {
int v[6] = {5};
for (int i = 0; i < 6; i++) {
printf("v[%d] = %d\n", i, v[i]);
}
return 0;
}
Sem valor inicial: lixo
Um array criado sem = dentro de uma função não é zerado. Cada portinha guarda o que estava naquele pedaço de memória antes, o famoso lixo:
#include <stdio.h>
int main(void) {
int estoque[8];
for (int i = 0; i < 8; i++) {
printf("estoque[%d] = %d\n", i, estoque[i]);
}
return 0;
}
$ gcc -std=c23 -Wall -Wextra lixo.c -o lixo
lixo.c: In function ‘main’:
lixo.c:6:9: warning: ‘estoque’ may be used uninitialized [-Wmaybe-uninitialized]
6 | printf("estoque[%d] = %d\n", i, estoque[i]);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lixo.c:4:9: note: ‘estoque’ declared here
4 | int estoque[8];
| ^~~~~~~
$ ./lixo
estoque[0] = 0
estoque[1] = 0
estoque[2] = 0
estoque[3] = 0
estoque[4] = 0
estoque[5] = 0
estoque[6] = 0
estoque[7] = 0 « zeros por sorte: nada garante isso
‘estoque’ may be used uninitialized = "estoque pode estar sendo usado sem valor inicial". No notebook da Ana saíram zeros, mas foi sorte: aquela memória ainda não tinha sido usada. No editor do guia, o mesmo programa mostrou estoque[0] = 1792246774 e estoque[2] = 483000000, e o Clang nem avisou. Ler um valor que nunca foi escrito é comportamento indefinido (aula Comportamento Indefinido).
Se você ainda não tem os valores, escreva = {} (ou = {0} antes do C23). A exceção é quando o próximo passo enche todas as posições, como o scanf do ler.c. (Arrays criados fora de qualquer função, os globais, já começam zerados: aula Escopo.)
Valores demais
O contrário, mais valores que portinhas, o gcc aceita com um aviso, e joga fora o que sobra:
$ cat demais.c
#include <stdio.h>
int main(void) {
int v[3] = {1, 2, 3, 4};
printf("%d\n", v[0]);
return 0;
}
$ gcc -std=c23 -Wall -Wextra demais.c -o demais
demais.c: In function ‘main’:
demais.c:4:26: warning: excess elements in array initializer
4 | int v[3] = {1, 2, 3, 4};
| ^
demais.c:4:26: note: (near initialization for ‘v’)
Excess elements in array initializer = "elementos em excesso no inicializador do array". O 4 foi descartado. Quase sempre o tamanho é que está errado: troque por int v[] = {1, 2, 3, 4}; e deixe o compilador contar.
7. O tamanho: número, constante ou VLA
No percorre.c, o número 5 aparece duas vezes: no int notas[5] e no i < 5. Se a turma ganhar um aluno e a Ana só mudar um dos dois, o programa erra. Há três jeitos de manter os dois iguais.
Jeito 1: uma constante com nome
#include <stdio.h>
#define ALUNOS 5
int main(void) {
int notas[ALUNOS] = {7, 8, 6, 9, 10};
int soma = 0;
for (int i = 0; i < ALUNOS; i++) {
soma = soma + notas[i];
}
printf("Média de %d alunos: %.1f\n", ALUNOS, (double) soma / ALUNOS);
return 0;
}
$ gcc -std=c23 -Wall -Wextra constante.c -o constante $ ./constante Média de 5 alunos: 8.0
#define ALUNOS 5 troca a palavra ALUNOS por 5 em todo o arquivo antes de compilar. Mudou a turma? Muda uma linha. No C23 também dá para escrever constexpr int ALUNOS = 5;, uma constante de verdade, com tipo. Os dois jeitos, e por que um simples const int não serve aqui, estão em Constantes.
Jeito 2: deixar o compilador contar
Com int notas[] = {…}, quem decide o tamanho é a lista. Para o laço saber quantos são, divida o tamanho do array em bytes pelo tamanho de um elemento:
#include <stdio.h>
int main(void) {
int notas[] = {7, 8, 6, 9, 10, 5};
size_t total = sizeof notas / sizeof notas[0];
int soma = 0;
for (size_t i = 0; i < total; i++) {
soma = soma + notas[i];
}
printf("%zu notas, média %.1f\n", total, (double) soma / total);
return 0;
}
$ gcc -std=c23 -Wall -Wextra conta.c -o conta $ ./conta 6 notas, média 7.5 « 24 bytes ÷ 4 bytes = 6 notas
- size_t total
size_té o tipo do resultado dosizeof: um inteiro sem sinal, próprio para tamanhos e contagens. Seu formato noprintfé%zu.- sizeof notas / sizeof notas[0]
- O array inteiro tem 24 bytes; um elemento, 4. A divisão dá 6. Acrescente uma nota na lista e o
totalvira 7 sozinho. - size_t i
- O índice com o mesmo tipo do
total, para oi < totalcomparar dois números do mesmo tipo (comint i, o-Wextraavisa:comparison of integer expressions of different signedness, "comparação de inteiros com e sem sinal").
A conta sizeof notas / sizeof notas[0] é explicada em Tamanho dos Tipos. Ela tem uma pegadinha: só funciona onde o array foi criado. Dentro de uma função que recebeu o array, ela dá um número errado (aula Parâmetros).
Jeito 3: um tamanho que só se sabe rodando (VLA) C99
E se a Ana só souber o tamanho da turma quando o programa perguntar? Dentro de uma função, o C99 permite um tamanho que é uma variável. Isso se chama VLA, variable length array, "array de tamanho variável":
#include <stdio.h>
int main(void) {
int n;
printf("Quantos alunos? ");
if (scanf("%d", &n) != 1 || n < 1 || n > 100) {
return 1;
}
int notas[n];
for (int i = 0; i < n; i++) {
printf("Nota %d: ", i + 1);
if (scanf("%d", ¬as[i]) != 1) {
return 1;
}
}
printf("\nO array tem %zu bytes (%d notas).\n", sizeof notas, n);
return 0;
}
$ gcc -std=c23 -Wall -Wextra vla.c -o vla $ ./vla Quantos alunos? 3 Nota 1: 7 Nota 2: 8 Nota 3: 9 O array tem 12 bytes (3 notas).
- n < 1 || n > 100
- Conferir o tamanho antes de criar o array. Um VLA de tamanho 0 ou negativo é comportamento indefinido, e um enorme estoura a pilha (8 MB no Linux) e derruba o programa.
- int notas[n];
- O array é criado nesta linha, com o tamanho que
ntiver agora. Depois de criado, o tamanho não muda, mesmo quenmude. - sizeof notas
- Com VLA, o
sizeofé calculado na hora: 3 × 4 = 12.
Os VLAs têm limites. Não aceitam valores iniciais: int v[n] = {1, 2, 3}; dá error: variable-sized object may not be initialized except with an empty initializer ("um objeto de tamanho variável não pode ser inicializado, a não ser com um inicializador vazio"); o = {} do C23 é aceito. E o C11 os tornou opcionais: o gcc e o Clang têm, mas o compilador da Microsoft (MSVC) não. Para tamanhos grandes ou que mudam enquanto o programa roda, o jeito certo é o malloc, no Nível 2 (malloc e free e Array que Cresce).
Qual usar
| Forma | Quando usar |
|---|---|
#define N 5 + int v[N] | O tamanho é conhecido e aparece em vários lugares (laços, mensagens). A mais comum. |
int v[] = {…} + sizeof v / sizeof v[0] | Uma lista fixa de valores escrita no código (tabela de preços, dias do mês). |
int v[n] (VLA) | Tamanho pequeno, lido na hora, e você conferiu o limite. Com cuidado. |
malloc | Tamanho grande ou que cresce enquanto o programa roda (Nível 2). |
int v[5] e i < 5 soltos | Só em exemplos curtos, como os desta aula. |
8. = e == não funcionam com arrays inteiros
Com variáveis comuns, b = a copia e a == b compara. Com arrays, nenhum dos dois faz isso.
Copiar com = não compila
#include <stdio.h>
int main(void) {
int a[3] = {7, 8, 9};
int b[3];
b = a;
printf("%d\n", b[0]);
return 0;
}
$ gcc -std=c23 -Wall -Wextra copiar.c -o copiar
copiar.c: In function ‘main’:
copiar.c:6:7: error: assignment to expression with array type
6 | b = a;
| ^
Assignment to expression with array type = "atribuição a uma expressão do tipo array". Em C, um array não pode receber outro array com =. O = com chaves só vale na criação (int b[3] = {7, 8, 9};), que é uma inicialização. No editor, o Clang diz array type 'int[3]' is not assignable ("o tipo array não pode receber atribuição"). Para copiar, copie elemento por elemento:
#include <stdio.h>
int main(void) {
int a[3] = {7, 8, 9};
int b[3];
for (int i = 0; i < 3; i++) {
b[i] = a[i];
}
b[0] = 100;
printf("a: %d %d %d\n", a[0], a[1], a[2]);
printf("b: %d %d %d\n", b[0], b[1], b[2]);
return 0;
}
$ gcc -std=c23 -Wall -Wextra copiar2.c -o copiar2 $ ./copiar2 a: 7 8 9 b: 100 8 9 « b é uma cópia de verdade: mudar b não muda a
Há também uma função pronta que copia bytes, a memcpy (de <string.h>): memcpy(b, a, sizeof a);. Ela aparece em Funções de String e no Nível 2.
Comparar com == compara outra coisa
#include <stdio.h>
int main(void) {
int a[3] = {7, 8, 9};
int b[3] = {7, 8, 9};
if (a == b) {
printf("Iguais\n");
} else {
printf("Diferentes\n");
}
return 0;
}
$ gcc -std=c23 -Wall -Wextra comparar.c -o comparar
comparar.c: In function ‘main’:
comparar.c:6:11: warning: comparison between two arrays [-Warray-compare]
6 | if (a == b) {
| ^~
comparar.c:6:11: note: use ‘&a[0] == &b[0]’ to compare the addresses
$ ./comparar
Diferentes « os valores são iguais, mas o == disse que não
Comparison between two arrays = "comparação entre dois arrays", e a nota sugere "use &a[0] == &b[0] para comparar os endereços". É isso que o == fez: perguntou "as duas fileiras começam no mesmo lugar da memória?". São duas fileiras diferentes, então a resposta é sempre "não", mesmo com os mesmos valores dentro. O Clang do editor é ainda mais direto: array comparison always evaluates to false ("comparar arrays sempre dá falso"). Para comparar os valores, use um laço que confere posição por posição e para na primeira diferença.
9. O índice que passa do fim
Agora o erro mais importante desta aula. O C não confere se o índice está dentro do array. Se você pedir notas[5] num array de 5, ele faz a conta "começo + 5 × 4 bytes" e lê o que estiver lá, que não é seu. Na maioria das vezes o compilador nem avisa.
Ler além do fim
Este é o erro do "um a mais" da aula for, agora com um array: <= no lugar de <.
#include <stdio.h>
int main(void) {
int notas[5] = {7, 8, 6, 9, 10};
int soma = 0;
for (int i = 0; i <= 5; i++) {
soma = soma + notas[i];
}
printf("Soma: %d\n", soma);
return 0;
}
$ gcc -std=c23 -Wall -Wextra soma-errada.c -o soma-errada « nenhum aviso $ ./soma-errada Soma: 40 « certo por sorte: depois do array havia um zero
O laço somou notas[5], que não existe. Desta vez, a memória logo depois do array tinha um zero, e a soma saiu certa. É o pior tipo de erro: o que se esconde. Num outro dia, com outras variáveis por perto, a soma viria errada.
O gcc só percebe quando você liga a otimização (-O2), porque é aí que ele analisa os laços a fundo:
$ gcc -std=c23 -Wall -Wextra -O2 soma-errada.c -o soma-errada
soma-errada.c: In function ‘main’:
soma-errada.c:7:28: warning: iteration 5 invokes undefined behavior [-Waggressive-loop-optimizations]
7 | soma = soma + notas[i];
| ~~~~~^~~
soma-errada.c:6:23: note: within this loop
6 | for (int i = 0; i <= 5; i++) {
| ~~^~~~
Iteration 5 invokes undefined behavior = "a volta 5 provoca comportamento indefinido", e a nota aponta o i <= 5. Para pegar o erro com certeza, na hora de rodar, compile com o AddressSanitizer (-fsanitize=address, aula Sanitizers):
$ gcc -std=c23 -Wall -Wextra -g -fsanitize=address soma-errada.c -o soma-errada
$ ./soma-errada
=================================================================
==417099==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7bb563df0034 at pc 0x55e12bd58477 bp 0x7ffd51901390 sp 0x7ffd51901380
READ of size 4 at 0x7bb563df0034 thread T0
#0 0x55e12bd58476 in main /home/ana/c/soma-errada.c:7
…
This frame has 1 object(s):
[32, 52) 'notas' (line 4) <== Memory access at offset 52 overflows this variable
…
SUMMARY: AddressSanitizer: stack-buffer-overflow /home/ana/c/soma-errada.c:7 in main
… « mais linhas com o mapa da memória
==417099==ABORTING
- stack-buffer-overflow
- "Estouro de buffer na pilha": um acesso passou do fim de um array local.
- READ of size 4
- Uma leitura de 4 bytes: um
int. - in main … soma-errada.c:7
- A linha culpada:
soma = soma + notas[i];. - [32, 52) 'notas' … offset 52 overflows this variable
- O array ocupa os bytes 32 a 51 (5 × 4 = 20 bytes). O acesso foi no byte 52, logo depois do fim: é o
notas[5].
Escrever além do fim
Ler fora do array dá um valor errado. Escrever fora é pior: estraga a memória de outra coisa. A Ana quer zerar 3 notas, mas usou <=:
#include <stdio.h>
int main(void) {
int pontos = 100;
int notas[3] = {7, 8, 9};
for (int i = 0; i <= 3; i++) {
notas[i] = 0;
}
printf("notas[0] = %d\n", notas[0]);
printf("pontos = %d\n", pontos);
return 0;
}
$ gcc -std=c23 -Wall -Wextra zerar.c -o zerar « nenhum aviso $ ./zerar notas[0] = 0 pontos = 100 *** stack smashing detected ***: terminated Abortado (imagem do núcleo gravada) ./zerar $ echo $? 134
O programa fez tudo e, na hora de terminar o main, morreu. Stack smashing detected = "esmagamento da pilha detectado". O gcc do Ubuntu coloca, logo depois das variáveis do main, um valor-sentinela chamado canário (como os canários que os mineiros levavam para a mina: se o passarinho passasse mal, havia gás). O notas[3] = 0 escreveu em cima dele. Quando o main terminou, o sistema conferiu o canário, viu que ele mudou e abortou o programa (código 134) em vez de continuar com a memória estragada.
Repare que o aviso vem no fim, longe da linha culpada. O -fsanitize=address, de novo, aponta na hora: WRITE of size 4 ("escrita de 4 bytes") em /home/ana/c/zerar.c:7, a linha notas[i] = 0;.
No editor (WebAssembly), os dois programas desta seção terminam normalmente: Soma: 40, e o zerar.c mostra pontos = 100 sem canário nem Abortado. Lá, o acesso fora do array passa calado (veja O Editor do Guia). Em compensação, o Clang do editor avisa quando o índice é um número fixo: printf("%d", notas[5]); dá warning: array index 5 is past the end of the array (that has type 'int[5]') ("o índice 5 passa do fim do array"), enquanto o gcc só avisa isso com -O2 (array subscript 5 is above array bounds of ‘int[5]’). Não confie no silêncio de nenhum dos dois: confira os limites você mesmo.
Percorra com for (int i = 0; i < TAMANHO; i++), com <. Se o índice vem de fora (do teclado, de um arquivo, de uma conta), confira antes de usar: if (i >= 0 && i < TAMANHO). E, enquanto estiver testando, compile com -g -fsanitize=address.
Quando dá errado
error: assignment to expression with array type
O que significa: você tentou copiar um array inteiro com = (b = a;), ou dar uma lista nova a um array já criado (notas = {1, 2, 3};).
Como consertar: copie com um laço (b[i] = a[i]) ou com memcpy(b, a, sizeof a). Lista entre chaves, só na criação.
warning: comparison between two arrays [-Warray-compare]
O que significa: o a == b compara os endereços das duas fileiras, não os valores. O resultado é sempre "diferentes".
Como consertar: compare elemento por elemento, num laço.
error: array size missing in ‘notas’
O que significa: você escreveu int notas[]; sem tamanho e sem lista. Com colchetes vazios, o compilador precisa das chaves para contar. (O Clang do editor diz definition of variable with array type needs an explicit size or an initializer: "precisa de um tamanho ou de valores iniciais".)
Como consertar: ponha o tamanho (int notas[5];) ou a lista (int notas[] = {7, 8};).
warning: excess elements in array initializer
O que significa: há mais valores entre as chaves do que portinhas no array. Os de sobra são descartados.
Como consertar: aumente o tamanho ou, melhor, use [] e deixe o compilador contar.
warning: ‘estoque’ may be used uninitialized [-Wmaybe-uninitialized]
O que significa: o array foi lido antes de receber valores. As portinhas têm lixo.
Como consertar: crie com = {} (ou = {0}), ou encha todas as posições antes de ler.
error: array subscript is not an integer
O que significa: o índice é um double ou float. Não existe a portinha 2,5.
Como consertar: use uma variável int (ou size_t) como índice. Se o valor vem de uma conta com casas decimais, converta: notas[(int) x] (aula Conversão de Tipos).
*** stack smashing detected *** e Abortado no fim do programa
O que significa: algum índice passou do fim do array, quase sempre um <= no laço. Ler fora dá valores errados; escrever fora estraga outras variáveis e o canário.
Como consertar: procure os laços com <= e os índices que vêm de fora. Compile com -g -fsanitize=address para o programa apontar a linha exata.
error: variable-sized object may not be initialized except with an empty initializer
O que significa: um array com tamanho variável (int v[n]) recebeu uma lista de valores.
Como consertar: use = {} ou encha com um laço. Se o tamanho na verdade é fixo, use #define ou constexpr em vez de uma variável (aula Constantes).
Referência rápida
| Escrita | O que faz |
|---|---|
int v[5]; | Array de 5 int, sem valores (lixo, se estiver dentro de uma função) |
int v[5] = {1, 2, 3, 4, 5}; | Array de 5 com todos os valores |
int v[] = {1, 2, 3}; | O compilador conta: 3 elementos |
int v[5] = {1, 2}; | 1 2 0 0 0: o que falta vira zero |
int v[5] = {}; / = {0}; | Tudo zero ({} é do C23; {0} funciona em todas as versões) |
int v[5] = {[3] = 9}; | Inicializador designado (C99): só a posição 3 vale 9 |
v[0] / v[4] | Primeiro e último elemento de um array de 5 |
v[i] = 10; | Troca o elemento da posição i |
scanf("%d", &v[i]); | Lê um número para a posição i |
for (int i = 0; i < N; i++) | Percorre um array de N elementos |
sizeof v / sizeof v[0] | Quantos elementos (só onde o array foi criado) |
int v[n]; | VLA: tamanho lido na hora (C99; opcional desde o C11) |
b = a; / a == b | Errados com arrays: copie e compare elemento por elemento |
-g -fsanitize=address | Opções do gcc que pegam índice fora do array ao rodar |
Exercícios
1. O último elemento
Complete para mostrar o último elemento de int dias[7]:
2. Percorrer
Complete a condição para percorrer um array de 10 elementos, sem passar do fim:
3. Tudo zero
Complete para criar um array de 20 double com todos os valores zero, do jeito do C23:
4. Depois de int v[5] = {4, 2};, quanto vale v[3]?
5. Um programa com for (int i = 0; i <= 5; i++) soma += notas[i]; (array de 5) compilou sem avisos e deu a soma certa. O que se pode concluir?
6. Qual destas linhas copia o array a (de 3 elementos) para b?
7. Por que, ao procurar o maior valor, o "campeão" começa como v[0] e não como 0?
Missão final: as temperaturas da semana
Missão: a Ana anotou a temperatura máxima de cada dia da semana. Complete o programa para mostrar a maior e a menor temperatura, com o dia de cada uma (o dia 1 é temperaturas[0]), e a média com uma casa decimal. Use um laço, e sem avisos.
Maior: 32 (dia 7) Menor: 25 (dia 5) Média: 28.9
#include <stdio.h>
#define DIAS 7
int main(void) {
int temperaturas[DIAS] = {28, 31, 30, 27, 25, 29, 32};
int maior = 0, dia_maior = 0;
int menor = 0, dia_menor = 0;
int soma = 0;
// percorra o array aqui
printf("Maior: %d (dia %d)\n", maior, dia_maior);
printf("Menor: %d (dia %d)\n", menor, dia_menor);
printf("Média: %.1f\n", (double) soma / DIAS);
return 0;
}
#include <stdio.h>
#define DIAS 7
int main(void) {
int temperaturas[DIAS] = {28, 31, 30, 27, 25, 29, 32};
int maior = temperaturas[0], dia_maior = 1;
int menor = temperaturas[0], dia_menor = 1;
int soma = 0;
for (int i = 0; i < DIAS; i++) {
if (temperaturas[i] > maior) {
maior = temperaturas[i];
dia_maior = i + 1;
}
if (temperaturas[i] < menor) {
menor = temperaturas[i];
dia_menor = i + 1;
}
soma = soma + temperaturas[i];
}
printf("Maior: %d (dia %d)\n", maior, dia_maior);
printf("Menor: %d (dia %d)\n", menor, dia_menor);
printf("Média: %.1f\n", (double) soma / DIAS);
return 0;
}
A solução faz as três receitas da seção 5 num laço só. O menor também começa com o primeiro elemento: se começasse com 0, nenhuma temperatura seria menor que ele e a resposta sairia Menor: 0, que nem está no array. A soma é 202, e 202 ÷ 7 = 28,857…, que o %.1f arredonda para 28.9.
Resumo
- Um array guarda vários valores do mesmo tipo, colados na memória, com um nome só:
int notas[5]. O tamanho é fixo depois de criado. - O índice começa em 0: num array de N, as posições vão de 0 a N−1. Cada elemento (
notas[i]) funciona como uma variável comum. - Percorra com
for (int i = 0; i < N; i++). As receitas básicas: somar, achar o maior (começando pelo primeiro elemento) e procurar (com um sinal como −1). - Inicialize sempre:
{1, 2, 3},[]para o compilador contar,= {}(C23) ou= {0}para zerar. O que falta na lista vira zero; sem lista nenhuma, é lixo. - Dê nome ao tamanho (
#define,constexpr) ou conte comsizeof v / sizeof v[0]. VLA (int v[n]) existe, mas com limites. b = anão compila ea == bcompara endereços: copie e compare elemento por elemento.- O C não confere o índice. Passar do fim é comportamento indefinido: pode dar certo por sorte, dar valor errado ou terminar em
stack smashing detected. Use<nos laços e-fsanitize=addressnos testes. No editor, esse erro passa calado.