C Arquivos Binários (fread e fwrite)
Até aqui, os arquivos do guia eram de texto: o número 1234567 virava os sete caracteres 1, 2, 3… e você podia abrir o arquivo no editor de textos. Existe outro jeito: copiar para o arquivo os bytes da memória, do jeitinho que o programa guarda a variável. Esse é o arquivo binário. Nesta aula você vai gravar o estoque da lojinha da Ana inteiro com uma linha de código, ler de volta, espiar os bytes com xxd e entender quando esse atalho é uma ótima ideia e quando é uma armadilha.
- A diferença entre arquivo de texto e arquivo binário, vista byte a byte.
- Como gravar com
fwritee ler comfread, e o que cada um dos quatro argumentos significa. - Como gravar um array de structs de uma vez e ler de volta, inteiro ou um registro por vez.
- Como olhar os bytes de um arquivo com
xxdeod. - Por que um arquivo binário pode não funcionar em outro computador (tamanho dos tipos, ordem dos bytes, ponteiros).
- Por que você precisa conferir o retorno de
freadefwrite.
Esta aula usa fopen, fclose e structs. Se precisar, reveja Arquivos (fopen e fclose) e Structs. 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.
A ideia: anotar × fotocopiar
Imagine que a Ana quer guardar a ficha de um produto. Ela tem dois jeitos:
- Anotar à mão num caderno: "Caderno, 12 unidades, R$ 15,90". Qualquer pessoa lê, mas dá trabalho escrever e, depois, alguém precisa ler e interpretar a anotação para pôr os números de volta na ficha.
- Tirar uma fotocópia da ficha. É instantâneo e a cópia é idêntica, mas só serve para quem tem uma ficha do mesmo modelo para comparar.
O arquivo de texto é a anotação: o fprintf transforma cada número em caracteres, e o fscanf (ou strtol) transforma de volta. O arquivo binário é a fotocópia: o fwrite copia os bytes da memória para o disco, sem conversão, e o fread copia do disco de volta para a memória.
| Arquivo de texto | Arquivo binário | |
|---|---|---|
| Grava com | fprintf, fputs | fwrite |
| Lê com | fgets, fscanf | fread |
| O número 1234567 ocupa | 7 bytes (um por algarismo) | 4 bytes (o tamanho de um int) |
| Abre no editor de textos? | Sim | Não: aparecem símbolos estranhos |
| Funciona em outro computador? | Quase sempre | Só se os tipos tiverem o mesmo tamanho e formato |
1. O mesmo número, dois arquivos
Para ver a diferença de verdade, o programa abaixo grava o número 1234567 duas vezes: em numero.txt com fprintf e em numero.bin com fwrite.
#include <stdio.h>
int main(void) {
int numero = 1234567;
FILE *txt = fopen("numero.txt", "w");
if (txt == NULL) {
printf("Não consegui criar numero.txt\n");
return 1;
}
fprintf(txt, "%d", numero);
fclose(txt);
FILE *bin = fopen("numero.bin", "wb");
if (bin == NULL) {
printf("Não consegui criar numero.bin\n");
return 1;
}
fwrite(&numero, sizeof numero, 1, bin);
fclose(bin);
printf("Pronto: numero.txt e numero.bin\n");
return 0;
}
- fopen("numero.bin", "wb")
- Abre para escrita (
w, de write) em modo binário (b, de binary). No Linux e no macOS obnão muda nada; no Windows ele é essencial (veja o quadro sobre o Windows). Use sempre: deixa claro que o arquivo é binário. - fwrite(
- "file write", escrever no arquivo. Copia bytes da memória para o arquivo, sem transformar nada em texto.
- &numero
- 1º argumento: onde estão os bytes. É o endereço da variável (o
&, como noscanf; veja Memória e Endereços). - sizeof numero
- 2º argumento: o tamanho de cada item, em bytes. Um
inttem 4 bytes. Nunca escreva o 4 à mão: osizeofacerta em qualquer computador. - 1
- 3º argumento: quantos itens gravar. Aqui, um
int. - bin
- 4º argumento: em qual arquivo. Repare que o arquivo vem no fim, ao contrário do
fprintf.
$ gcc -std=c23 -Wall -Wextra numero.c -o numero $ ./numero Pronto: numero.txt e numero.bin $ ls -l numero.txt numero.bin -rw-rw-r-- 1 ana ana 4 out 17 10:32 numero.bin « 4 bytes: um int -rw-rw-r-- 1 ana ana 7 out 17 10:32 numero.txt « 7 bytes: sete algarismos $ cat numero.txt; echo 1234567
O numero.txt é legível. Já o numero.bin, se você mandar o cat mostrar, aparece como lixo na tela, porque seus bytes não são letras. Para ver o que tem dentro, use um visualizador de bytes.
Espiando os bytes com xxd e od
O xxd faz um hex dump ("despejo em hexadecimal"): mostra cada byte como dois algarismos hexadecimais (de 00 a ff). O od (octal dump) faz o mesmo e ainda sabe interpretar os bytes como números.
$ xxd numero.txt 00000000: 3132 3334 3536 37 1234567 $ xxd numero.bin 00000000: 87d6 1200 .... $ od -A d -t d4 numero.bin 0000000 1234567 0000004
- 00000000:
- A posição (em hexadecimal) do primeiro byte da linha. O primeiro byte do arquivo é a posição 0.
- 3132 3334 3536 37
- Os bytes do arquivo de texto:
31é o código do caractere'1',32é o'2', e assim por diante (a tabela ASCII, da aula Caracteres). À direita, oxxdmostra esses bytes como letras. - 87d6 1200
- Os 4 bytes do
int:87 d6 12 00. Nenhum deles é uma letra imprimível, por isso a coluna da direita mostra pontos. - od -A d -t d4
- Mostra as posições em decimal (
-A d) e lê o arquivo de 4 em 4 bytes como inteiros com sinal (-t d4). Ele reconstruiu o 1234567: os bytes estão lá, só não estão "escritos".
Por que 87 d6 12 00? Em hexadecimal, 1234567 é 0x0012D687. Os bytes estão de trás para frente: o byte menos importante (87) vem primeiro. Esse jeito de guardar se chama little-endian ("a ponta pequena primeiro") e é o dos processadores Intel, AMD e da maioria dos ARM. Voltaremos a isso na seção de portabilidade.
od -A d -t x1 arquivo mostra os mesmos bytes em hexadecimal, um por um (87 d6 12 00), e od -c arquivo mostra cada byte como caractere quando dá. Use o que achar mais fácil de ler; o xxd vem junto com o editor vim, e o od existe em qualquer Linux.
2. Gravar um array de structs com fwrite
A grande vantagem do fwrite aparece com structs. Com texto, você teria de escrever cada campo com o seu formato (%s, %d, %.2f) e, na hora de ler, separar tudo de novo. Com binário, o estoque inteiro vai para o disco numa linha:
#include <stdio.h>
typedef struct {
char nome[20];
int quantidade;
double preco;
} Produto;
int main(void) {
Produto estoque[3] = {
{"Caderno", 12, 15.90},
{"Caneta", 40, 2.50},
{"Borracha", 25, 1.75},
};
FILE *f = fopen("estoque.bin", "wb");
if (f == NULL) {
printf("Não consegui criar estoque.bin\n");
return 1;
}
size_t gravados = fwrite(estoque, sizeof(Produto), 3, f);
fclose(f);
if (gravados != 3) {
printf("Erro: só gravei %zu de 3 produtos\n", gravados);
return 1;
}
printf("Gravei %zu produtos de %zu bytes cada.\n", gravados, sizeof(Produto));
return 0;
}
| Linha | O que faz |
|---|---|
typedef struct { … } Produto; | A ficha de um produto: nome (até 19 letras e o '\0'), quantidade e preço (veja typedef). |
fwrite(estoque, …) | O nome do array já é o endereço do primeiro elemento, então não leva & (aula Ponteiros e Arrays). |
sizeof(Produto) | Cada item tem o tamanho de um Produto: 32 bytes neste computador. |
3 | Três itens, um depois do outro: 3 × 32 = 96 bytes. |
size_t gravados = … | O fwrite devolve quantos itens gravou por inteiro. Se for menos que 3, algo deu errado (disco cheio, por exemplo). |
%zu | O formato do printf para size_t (aula Formatos do printf). |
$ gcc -std=c23 -Wall -Wextra gravar.c -o gravar $ ./gravar Gravei 3 produtos de 32 bytes cada. $ ls -l estoque.bin -rw-rw-r-- 1 ana ana 96 out 17 10:32 estoque.bin
96 bytes, como a conta previa. Agora veja como esses bytes estão organizados:
$ xxd estoque.bin 00000000: 4361 6465 726e 6f00 0000 0000 0000 0000 Caderno......... 00000010: 0000 0000 0c00 0000 cdcc cccc cccc 2f40 ............../@ 00000020: 4361 6e65 7461 0000 0000 0000 0000 0000 Caneta.......... 00000030: 0000 0000 2800 0000 0000 0000 0000 0440 ....(..........@ 00000040: 426f 7272 6163 6861 0000 0000 0000 0000 Borracha........ 00000050: 0000 0000 1900 0000 0000 0000 0000 fc3f ...............?
Cada produto ocupa exatamente duas linhas do xxd (32 bytes). Olhando o primeiro:
- Bytes 0 a 19 (
nome):43 61 64 65 72 6e 6fsão as letras de "Caderno", seguidas do00que termina a string e de zeros até completar os 20 bytes do array. O nome curto ainda ocupa 20 bytes: no arquivo binário, cada campo tem tamanho fixo. - Bytes 20 a 23 (
quantidade):0c 00 00 00.0x0cé 12, de novo com o byte pequeno primeiro. - Bytes 24 a 31 (
preco):cd cc cc cc cc cc 2f 40. É odouble15.90 no formato interno dos números com vírgula (o padrão IEEE 754, da aula float e double). Não dá para ler de olho, e não precisa: ofreadpõe esses bytes de volta numdouble.
3. Ler de volta com fread
O fread ("file read", ler do arquivo) é o espelho do fwrite: os mesmos quatro argumentos, mas os bytes vão do arquivo para a memória.
#include <stdio.h>
typedef struct {
char nome[20];
int quantidade;
double preco;
} Produto;
int main(void) {
Produto estoque[10];
FILE *f = fopen("estoque.bin", "rb");
if (f == NULL) {
printf("Não consegui abrir estoque.bin\n");
return 1;
}
size_t lidos = fread(estoque, sizeof(Produto), 10, f);
fclose(f);
printf("Li %zu produtos:\n", lidos);
for (size_t i = 0; i < lidos; i++) {
printf("%-10s %3d R$ %6.2f\n", estoque[i].nome,
estoque[i].quantidade, estoque[i].preco);
}
return 0;
}
- Produto estoque[10];
- Espaço para até 10 produtos. O programa não sabe quantos existem no arquivo.
- "rb"
- Leitura (
r, de read) em modo binário. - fread(estoque, sizeof(Produto), 10, f)
- "Leia até 10 itens de
sizeof(Produto)bytes e ponha a partir deestoque." O arquivo só tem 3, e não tem problema: ofreadlê o que houver. - size_t lidos
- Quantos itens completos foram lidos: 3. É esse número, e não o 10, que o laço usa.
- %-10s
- O nome alinhado à esquerda numa coluna de 10 caracteres (veja Formatos do printf).
$ gcc -std=c23 -Wall -Wextra ler.c -o ler $ ./ler Li 3 produtos: Caderno 12 R$ 15.90 Caneta 40 R$ 2.50 Borracha 25 R$ 1.75
Outro programa, compilado separadamente, leu o arquivo e recuperou os três produtos sem converter nada. Ele funciona porque declara a mesma struct Produto que o gravar.c. Os dois programas precisam concordar, byte a byte, sobre o formato da ficha.
Um registro por vez
Quando o arquivo pode ser grande, ou você não sabe quantos registros tem, leia um por vez num laço, até o fread devolver 0:
#include <stdio.h>
typedef struct {
char nome[20];
int quantidade;
double preco;
} Produto;
int main(void) {
FILE *f = fopen("estoque.bin", "rb");
if (f == NULL) {
printf("Não consegui abrir estoque.bin\n");
return 1;
}
Produto p;
double total = 0.0;
while (fread(&p, sizeof p, 1, f) == 1) {
printf("%-10s %3d x R$ %5.2f\n", p.nome, p.quantidade, p.preco);
total += p.quantidade * p.preco;
}
if (ferror(f)) {
printf("Erro de leitura!\n");
} else {
printf("Valor do estoque: R$ %.2f\n", total);
}
fclose(f);
return 0;
}
- while (fread(&p, sizeof p, 1, f) == 1)
- Lê um produto para dentro de
p. Enquanto conseguir (devolver 1), o laço continua. No fim do arquivo, ofreaddevolve 0 e o laço para. - &p
- Aqui é uma variável comum, então precisa do
&. Com array (estoque), não. - ferror(f)
- Depois do laço: o
freadparou porque o arquivo acabou ou porque houve um erro (disco com defeito, pendrive arrancado)? Oferrorresponde "verdadeiro" só no segundo caso. O irmão dele,feof(f), responde se chegou ao fim.
$ gcc -std=c23 -Wall -Wextra ler1.c -o ler1 $ ./ler1 Caderno 12 x R$ 15.90 Caneta 40 x R$ 2.50 Borracha 25 x R$ 1.75 Valor do estoque: R$ 334.55
Experimente: este programa grava 4 notas em notas.bin e lê de volta num array novo. Clique em Rodar. Depois troque o 4 do fread por 10 (e o tamanho de copia para 10): quantas notas ele diz que leu? E se o fwrite gravar só 2?
#include <stdio.h>
int main(void) {
int notas[4] = {7, 9, 6, 10};
// 1) grava as 4 notas de uma vez
FILE *f = fopen("notas.bin", "wb");
if (f == NULL) {
printf("Não consegui criar notas.bin\n");
return 1;
}
size_t gravadas = fwrite(notas, sizeof(int), 4, f);
fclose(f);
printf("Gravei %zu notas.\n", gravadas);
// 2) lê de volta num array novo
int copia[4] = {0};
f = fopen("notas.bin", "rb");
if (f == NULL) {
printf("Não consegui abrir notas.bin\n");
return 1;
}
size_t lidas = fread(copia, sizeof(int), 4, f);
fclose(f);
printf("Li %zu notas:", lidas);
for (size_t i = 0; i < lidas; i++) {
printf(" %d", copia[i]);
}
printf("\n");
return 0;
}
4. Confira sempre o retorno
fread e fwrite nunca avisam por conta própria que algo deu errado: eles só devolvem um número, e é você quem precisa olhar. Para ver por que isso importa, a Ana cortou o estoque.bin no byte 80 (como se a gravação tivesse sido interrompida no meio do terceiro produto). O comando truncate -s 80 faz esse corte:
$ cp estoque.bin backup.bin « guarda uma cópia antes $ truncate -s 80 estoque.bin $ ls -l estoque.bin -rw-rw-r-- 1 ana ana 80 out 17 10:33 estoque.bin $ ./ler Li 2 produtos: Caderno 12 R$ 15.90 Caneta 40 R$ 2.50 $ ./ler1 Caderno 12 x R$ 15.90 Caneta 40 x R$ 2.50 Valor do estoque: R$ 290.80 $ cp backup.bin estoque.bin « devolve o arquivo inteiro
80 bytes são dois produtos completos (64 bytes) e metade do terceiro. O fread devolveu 2: ele só conta itens inteiros, e o pedaço que sobrou foi descartado. Os dois programas se comportaram bem porque usam o número devolvido. Um programa que ignorasse o retorno e mostrasse sempre 3 produtos imprimiria lixo no terceiro.
| O que voltou | O que significa | O que fazer |
|---|---|---|
| Igual à quantidade pedida | Deu tudo certo | Seguir em frente |
Menor (no fread) | O arquivo acabou (feof) ou houve erro (ferror) | Usar só os itens lidos; se for erro, avisar |
Menor (no fwrite) | Erro de escrita: disco cheio, sem permissão, pendrive removido | Avisar e não fingir que salvou (o motivo está em errno) |
fclose também pode falhar. O fwrite costuma guardar os bytes num buffer (uma área de espera na memória) e só manda para o disco depois. Se o disco estiver cheio, o erro pode aparecer só no fclose, que devolve EOF em vez de 0. Em programas que salvam dados importantes, confira também: if (fclose(f) != 0) { … }.
5. Portabilidade: a fotocópia só serve para a mesma ficha
O arquivo binário guarda os bytes como este computador e este compilador os organizam. Outro computador pode organizar diferente. Há três motivos.
O tamanho dos tipos muda
O C só garante tamanhos mínimos (aula Inteiros). Um long tem 8 bytes no Linux de 64 bits e 4 bytes no Windows e no editor do guia. Veja a mesma struct nos dois:
#include <stdio.h>
typedef struct {
long codigo;
int quantidade;
} Item;
int main(void) {
printf("sizeof(long) = %zu\n", sizeof(long));
printf("sizeof(Item) = %zu\n", sizeof(Item));
return 0;
}
$ gcc -std=c23 -Wall -Wextra portavel.c -o portavel $ ./portavel sizeof(long) = 8 sizeof(Item) = 16
No editor do guia (Clang em WebAssembly de 32 bits), o mesmo programa mostra sizeof(long) = 4 e sizeof(Item) = 8. Um arquivo de Item gravado num lado seria lido pela metade no outro. Repare também que 8 + 4 deu 16, e não 12: o compilador pode deixar bytes de enchimento (padding) entre os campos ou no fim da struct, para alinhar os números na memória (veja Structs). Esse enchimento também vai para o arquivo.
Solução: nos campos gravados em binário, use os tipos de tamanho fixo de <stdint.h>: int32_t tem 32 bits (4 bytes) em qualquer lugar, int64_t tem 64 bits (aula Inteiros de Tamanho Fixo).
A ordem dos bytes muda
Você viu que 1234567 ficou 87 d6 12 00, com o byte pequeno primeiro (little-endian). Alguns processadores antigos ou de rede (e vários formatos de arquivo e protocolos da internet) usam a ordem contrária, big-endian: 00 12 d6 87. Um arquivo gravado num e lido no outro daria um número completamente diferente. Hoje quase todo computador e celular é little-endian, por isso o problema aparece pouco, mas aparece quando você lê um formato definido por outra pessoa (imagens, áudio, arquivos de rede).
Ponteiros não fazem sentido no disco
Um ponteiro é um endereço da memória deste programa, nesta execução. Se a struct tem um char *nome, o fwrite grava o endereço, e não o texto. O exemplo está em Quando dá errado.
Texto ou binário: qual usar?
| Situação | Prefira | Por quê |
|---|---|---|
| Uma pessoa vai ler ou editar o arquivo | Texto (CSV, uma informação por linha) | Abre em qualquer editor e em planilhas |
| Outro programa, outra linguagem ou outro computador vai ler | Texto | Não depende de tamanho de tipo nem de ordem dos bytes |
| O mesmo programa grava e lê, na mesma máquina (um "save" de jogo, um cache) | Binário | Uma linha para gravar, uma para ler, sem conversão |
| Muitos números e velocidade importa | Binário com int32_t/int64_t | Menor e mais rápido, com tamanhos garantidos |
| Precisa pular direto para o registro número 500 | Binário com registros de tamanho fixo | A posição é uma conta simples (aula fseek e ftell) |
b importa no Windows. No Windows, um arquivo aberto em modo texto ("r", "w") troca cada '\n' por dois bytes (\r\n) ao gravar e faz o caminho contrário ao ler. Num arquivo binário, qualquer byte 0a (o valor de '\n') no meio de um número seria "corrigido" e estragaria o dado. Com "wb" e "rb", nenhum byte é trocado. No Linux e no macOS não existe essa troca, mas escrever o b deixa o programa correto em todos os sistemas.
Quando dá errado
Falha de segmentação (imagem do núcleo gravada) logo no fread
#include <stdio.h>
int main(void) {
int notas[3];
FILE *f = fopen("notas.bin", "rb");
fread(notas, sizeof(int), 3, f);
printf("Primeira nota: %d\n", notas[0]);
fclose(f);
return 0;
}
$ gcc -std=c23 -Wall -Wextra semteste.c -o semteste $ ./semteste Falha de segmentação (imagem do núcleo gravada) ./semteste
O que significa: o arquivo notas.bin não existe nesta pasta, o fopen devolveu NULL, e o fread tentou ler de um ponteiro nulo. O compilador não avisa nada: o erro só aparece ao rodar. No editor do guia, a mesma falha termina com o código de saída 139.
Como consertar: confira o fopen sempre, antes de usar o arquivo: if (f == NULL) { … return 1; }. Para dizer por que não abriu, use perror (aula errno e perror).
warning: ‘sizeof’ on array function parameter ‘lista’ will return size of ‘Produto *’
void salvar(FILE *f, Produto lista[], int n) {
fwrite(lista, sizeof(lista), n, f);
}
$ gcc -std=c23 -Wall -Wextra param.c -o param
param.c: In function ‘salvar’:
param.c:10:25: warning: ‘sizeof’ on array function parameter ‘lista’ will return size of ‘Produto *’ [-Wsizeof-array-argument]
10 | fwrite(lista, sizeof(lista), n, f);
| ^
param.c:9:30: note: declared here
9 | void salvar(FILE *f, Produto lista[], int n) {
| ~~~~~~~~^~~~~~~
$ ./param
$ ls -l copia.bin
-rw-rw-r-- 1 ana ana 24 out 17 10:33 copia.bin « deveria ter 96 bytes
O que significa: dentro da função, lista não é um array, é um ponteiro (aula Ponteiros e Arrays). sizeof(lista) deu 8 (o tamanho de um ponteiro), e o programa gravou 3 × 8 = 24 bytes de pedaços de produtos. O Clang do editor diz o mesmo: sizeof on array function parameter will return size of 'Produto *' instead of 'Produto[]'.
Como consertar: o tamanho de cada item é o tamanho do tipo: sizeof(Produto) ou sizeof lista[0].
typedef struct {
char *nome; // ponteiro, e não array!
int quantidade;
} Item;
/* … */
Item it = {"Caderno", 12};
fwrite(&it, sizeof it, 1, f);
$ ./ponteiro sizeof(Item) = 16 $ xxd item.bin 00000000: 0400 3344 6e62 0000 0c00 0000 0000 0000 ..3Dnb..........
O que significa: não há "Caderno" no arquivo. Os primeiros 8 bytes são o endereço onde o texto estava na memória (e ele muda a cada execução); depois vem o 12 (0c) e o enchimento. Outro programa que lesse esse arquivo teria um ponteiro para um lugar que não existe, e o mais provável é uma falha de segmentação ao usar nome.
Como consertar: nas structs que vão para arquivo binário, use arrays de tamanho fixo (char nome[20];). Se o texto pode ser grande, grave antes o tamanho (um int32_t) e depois os caracteres, e leia na mesma ordem.
A Ana mudou a ordem dos campos no programa de leitura (int quantidade; antes de char nome[20];) e leu o mesmo estoque.bin:
$ ./ler_errado rno 1701077315 x R$ 15.90 ta 1701732675 x R$ 2.50 acha 1920102210 x R$ 1.75
O que significa: o fread não sabe o que é nome nem quantidade; ele só copia 32 bytes. Os 4 primeiros bytes ("Cade") foram parar em quantidade (1701077315 é "Cade" lido como número), e o nome começou no 5º byte ("rno"). O preço por acaso caiu no lugar certo. Nenhum erro, nenhum aviso: só dados errados.
Como consertar: escreva a struct num só lugar, num arquivo .h que os dois programas incluem (aula Arquivos .h e .c). Se o formato mudar, arquivos antigos deixam de servir: muitos programas gravam no começo do arquivo um número de versão para detectar isso.
O que significa: você usou fwrite com um número e esperava ver o número escrito. O fwrite grava os bytes da memória, que não são letras.
Como consertar: se o arquivo é para gente ler, use fprintf (aula Escrever Arquivos). Se é binário mesmo, olhe com xxd ou od, e não com cat.
Referência rápida
| Escrita | O que faz |
|---|---|
fopen(nome, "wb") | Cria (ou apaga e recria) um arquivo binário para escrita |
fopen(nome, "rb") | Abre um arquivo binário para leitura |
fopen(nome, "ab") | Abre para acrescentar no fim (cria se não existir) |
fopen(nome, "r+b") | Abre para ler e escrever, sem apagar (aula fseek) |
fwrite(ptr, tam, n, f) | Grava n itens de tam bytes a partir de ptr; devolve quantos gravou |
fread(ptr, tam, n, f) | Lê até n itens de tam bytes para ptr; devolve quantos leu inteiros |
fwrite(&x, sizeof x, 1, f) | Grava uma variável |
fwrite(v, sizeof v[0], n, f) | Grava n elementos de um array |
while (fread(&x, sizeof x, 1, f) == 1) | Lê um registro por vez até o fim |
feof(f) / ferror(f) | Depois de uma leitura curta: acabou o arquivo? / houve erro? |
xxd arq, od -A d -t x1 arq | No terminal: mostra os bytes do arquivo em hexadecimal |
Exercícios
1. Gravar uma variável
Complete para gravar a variável double saldo no arquivo f:
2. O modo certo
Complete o modo do fopen para ler um arquivo binário:
3. Tamanho do arquivo
Uma struct tem sizeof igual a 32. Quantos bytes tem um arquivo com 25 dessas structs gravadas com fwrite?
4. O arquivo tem 2 registros completos e o programa chama fread(v, sizeof v[0], 5, f). O que o fread devolve?
5. O xxd mostrou 0c00 0000 no lugar de um int. Que número é esse num computador little-endian?
6. Qual struct é segura para gravar com fwrite e ler em outra execução do programa?
Missão final: o boletim da turma
Missão: a parte que grava a turma em turma.bin já está pronta. Escreva a leitura: abra o arquivo com "rb" (conferindo o NULL), leia um aluno por vez com fread num laço, mostre cada um e calcule a média. A saída deve ser:
Ana: 9.5 Bruno: 6.0 Carla: 8.0 Davi: 6.5 Média: 7.50
#include <stdio.h>
typedef struct {
char nome[16];
double nota;
} Aluno;
int main(void) {
Aluno turma[4] = {
{"Ana", 9.5}, {"Bruno", 6.0}, {"Carla", 8.0}, {"Davi", 6.5},
};
FILE *f = fopen("turma.bin", "wb");
if (f == NULL) {
printf("Não consegui criar turma.bin\n");
return 1;
}
fwrite(turma, sizeof(Aluno), 4, f);
fclose(f);
// agora leia turma.bin de volta, um aluno por vez
double soma = 0.0;
int lidos = 0;
if (lidos > 0) {
printf("Média: %.2f\n", soma / lidos);
}
return 0;
}
#include <stdio.h>
typedef struct {
char nome[16];
double nota;
} Aluno;
int main(void) {
Aluno turma[4] = {
{"Ana", 9.5}, {"Bruno", 6.0}, {"Carla", 8.0}, {"Davi", 6.5},
};
FILE *f = fopen("turma.bin", "wb");
if (f == NULL) {
printf("Não consegui criar turma.bin\n");
return 1;
}
fwrite(turma, sizeof(Aluno), 4, f);
fclose(f);
// agora leia turma.bin de volta, um aluno por vez
double soma = 0.0;
int lidos = 0;
f = fopen("turma.bin", "rb");
if (f == NULL) {
printf("Não consegui abrir turma.bin\n");
return 1;
}
Aluno a;
while (fread(&a, sizeof a, 1, f) == 1) {
printf("%s: %.1f\n", a.nome, a.nota);
soma += a.nota;
lidos++;
}
fclose(f);
if (lidos > 0) {
printf("Média: %.2f\n", soma / lidos);
}
return 0;
}
Se quiser ir além: troque o fwrite para gravar só 3 alunos e veja a média mudar sozinha, porque o laço de leitura se adapta ao que existe no arquivo.
Resumo
- Arquivo de texto guarda os números escritos (
fprintf); arquivo binário guarda os bytes da memória (fwrite). Use os modos"wb","rb","ab". fwrite(ptr, tamanho, quantidade, arquivo)efread(ptr, tamanho, quantidade, arquivo); o tamanho vem desizeof.- Um array de structs vai e volta com uma linha. Para ler sem saber quantos há, use
while (fread(&x, sizeof x, 1, f) == 1). - Os dois devolvem quantos itens inteiros transferiram: confira sempre. Depois de uma leitura curta,
feofeferrordizem o motivo. xxdeodmostram os bytes. Os processadores comuns são little-endian: 12 vira0c 00 00 00.- Binário não é portátil: o tamanho dos tipos, o enchimento e a ordem dos bytes mudam. Use
int32_t/int64_t, arrays de tamanho fixo e nunca ponteiros. Para trocar dados com outros programas, prefira texto.