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.

Você vai aprender
  • 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 = a e a == b nã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
Os colchetes têm dois papéis

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]
Valor786910
Distância do começo0 bytes4 bytes8 bytes12 bytes16 bytes
Para a Anaaluno 1aluno 2aluno 3aluno 4aluno 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]++; ou notas[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 por 5) 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", &notas[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 scanf vai enchê-las.
&notas[i]
"O endereço do elemento i": o lugar onde o scanf deve escrever (aula Memória e Endereços). Os colchetes são resolvidos primeiro: é o endereço de notas[i], e não de notas.
!= 1
Se o scanf nã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 posicao guarda 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 break sai do laço na hora (aula break e continue).
if (achou >= 0)
Depois do laço, se achou deixou 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).

Regra do guia: todo array começa com valor

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 do sizeof: um inteiro sem sinal, próprio para tamanhos e contagens. Seu formato no printf é %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 total vira 7 sozinho.
size_t i
O índice com o mesmo tipo do total, para o i < total comparar dois números do mesmo tipo (com int i, o -Wextra avisa: 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", &notas[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 n tiver agora. Depois de criado, o tamanho não muda, mesmo que n mude.
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

FormaQuando 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.
mallocTamanho grande ou que cresce enquanto o programa roda (Nível 2).
int v[5] e i < 5 soltosSó 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 do guia

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.

Regra do guia: todo índice fica entre 0 e tamanho − 1

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).

Resultado errado ou estranho, sem nenhum aviso; ou *** 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

EscritaO 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 == bErrados com arrays: copie e compare elemento por elemento
-g -fsanitize=addressOpçõ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]:

printf("%d\n", dias[]);

2. Percorrer

Complete a condição para percorrer um array de 10 elementos, sem passar do fim:

for (int i = 0; ; i++)

3. Tudo zero

Complete para criar um array de 20 double com todos os valores zero, do jeito do C23:

double saldo[20] = ;

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 com sizeof v / sizeof v[0]. VLA (int v[n]) existe, mas com limites.
  • b = a não compila e a == b compara 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=address nos testes. No editor, esse erro passa calado.