C Arquivos (fopen e fclose)

Tudo o que os seus programas guardaram até agora morava em variáveis, e as variáveis somem quando o programa termina. A lista de compras da Ana, as notas da turma, o estoque da lojinha: rodou de novo, começou do zero. Para uma informação sobreviver, ela precisa ir para um arquivo no disco. Nesta aula você vai aprender os dois gestos que começam e terminam todo trabalho com arquivos em C: abrir com fopen e fechar com fclose. Ler e escrever de verdade ficam para as próximas aulas, Ler Arquivos e Escrever Arquivos.

Você vai aprender
  • O que é o tipo FILE * e por que você nunca mexe dentro dele.
  • Como abrir um arquivo com fopen e por que sempre conferir se voltou NULL.
  • Como fechar com fclose, e o que acontece quando você esquece ou fecha duas vezes.
  • Os modos de abertura: "r", "w", "a", os com +, o b e o "wx" do C11.
  • Em que pasta o arquivo é criado, e a diferença entre caminho relativo e absoluto.

Todos os programas desta aula 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 aula usa ponteiros e o NULL; se precisar, reveja Ponteiro Nulo.


A ideia: pegar um caderno na estante

Pense num caderno guardado na estante. Para anotar alguma coisa, a Ana:

  1. pega o caderno e abre na página certa (para ler do começo, ou na primeira página em branco, para continuar);
  2. lê ou escreve;
  3. fecha o caderno e põe de volta na estante, para não amassar e para o próximo encontrar no lugar.

Em C é igual. O fopen pega e abre o arquivo; as funções das próximas aulas leem e escrevem; o fclose fecha. E, como na vida real, pode acontecer de o caderno não estar na estante: o programa precisa estar preparado para isso.

Com o cadernoEm C
Pegar e abrirFILE *arq = fopen("recado.txt", "w");
Dizer se é para ler, escrever do zero ou continuaro modo: "r", "w", "a"
O dedo marcando a página onde você estáo FILE *, que guarda a posição
"O caderno não está na estante"fopen devolve NULL
Fechar e guardarfclose(arq);

1. O primeiro arquivo

Este programa cria o arquivo recado.txt e escreve uma linha nele:

#include <stdio.h>

int main(void) {
    FILE *arq = fopen("recado.txt", "w");
    if (arq == NULL) {
        printf("Não consegui criar recado.txt\n");
        return 1;
    }

    fputs("Comprar pão e leite.\n", arq);
    fclose(arq);

    printf("Recado gravado!\n");
    return 0;
}
#include <stdio.h>
O mesmo cabeçalho do printf: stdio quer dizer "standard input/output", entrada e saída padrão. Tudo o que é de arquivo de texto está nele.
FILE *arq
Uma variável do tipo ponteiro para FILE. O FILE (assim mesmo, em maiúsculas) é uma struct da biblioteca com tudo o que ela precisa para cuidar do arquivo aberto. Você só guarda o ponteiro e o entrega às funções. O nome arq é escolha sua (muita gente usa f ou fp).
fopen("recado.txt", "w")
"file open", abrir arquivo. O 1º argumento é o nome (ou caminho) do arquivo; o 2º é o modo, um texto entre aspas duplas. "w" (de write, escrever) cria o arquivo, ou apaga o que havia nele, e deixa pronto para escrita.
if (arq == NULL)
Se não deu para abrir, o fopen devolve NULL. É obrigatório conferir: usar um NULL como arquivo derruba o programa (veja "Quando dá errado").
return 1;
Termina avisando ao sistema que algo falhou (qualquer número diferente de 0).
fputs("…", arq)
"file put string", pôr um texto no arquivo. É como o printf, mas vai para o arquivo em vez da tela. A aula Escrever Arquivos mostra todas as formas.
fclose(arq)
"file close", fechar o arquivo. Termina de gravar o que estava pendente e devolve os recursos ao sistema. Depois dele, arq não pode mais ser usado.
$ gcc -std=c23 -Wall -Wextra recado.c -o recado
$ ./recado
Recado gravado!
$ ls -l recado.txt
-rw-rw-r-- 1 ana ana 22 out 17 11:32 recado.txt
$ cat recado.txt
Comprar pão e leite.

O arquivo apareceu na pasta, com 22 bytes, e o cat mostra o que tem dentro. São 20 letras visíveis, mas o ã ocupa 2 bytes em UTF-8 (aula Acentos e UTF-8) e o \n do fim também conta: 22. O programa terminou, mas o recado ficou: rode cat recado.txt amanhã e ele ainda estará lá.


2. Quando o arquivo não abre

Agora o contrário: abrir para ler ("r", de read) um arquivo que ainda não existe.

#include <stdio.h>

int main(void) {
    FILE *arq = fopen("diario.txt", "r");
    if (arq == NULL) {
        printf("Não consegui abrir diario.txt\n");
        return 1;
    }
    printf("Abri o diário!\n");
    fclose(arq);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra abrir.c -o abrir
$ ./abrir
Não consegui abrir diario.txt
$ echo $?
1

O "r" nunca cria nada: se o arquivo não existe, o fopen devolve NULL, e o nosso if cuidou disso. O echo $? mostra o código com que o programa terminou: o 1 do return 1.

Dizer o motivo: perror

"Não consegui abrir" é pouco. O arquivo não existe? Existe, mas é proibido? Quando o fopen falha, ele deixa o motivo anotado, e a função perror ("print error", mostrar o erro) escreve esse motivo depois do texto que você passar:

#include <stdio.h>

int main(void) {
    FILE *arq = fopen("diario.txt", "r");
    if (arq == NULL) {
        perror("diario.txt");
        return 1;
    }

    char linha[100];
    if (fgets(linha, sizeof linha, arq) != NULL) {
        printf("Primeira linha: %s", linha);
    }
    fclose(arq);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra abrir2.c -o abrir2
$ ./abrir2
diario.txt: No such file or directory

No such file or directory = "arquivo ou diretório inexistente". Esse texto vem do sistema, em inglês (a aula errno e perror explica por que, e como saber o motivo dentro do programa). O fgets lê uma linha do arquivo, como você já fez com o teclado na aula Ler uma Linha: a aula Ler Arquivos mostra os detalhes.

Os motivos mais comuns de um NULL
  • No such file or directory: o arquivo (ou a pasta do caminho) não existe, ou o nome está errado. Maiúsculas contam: Diario.txt não é diario.txt.
  • Permission denied: o arquivo existe, mas você não tem permissão para o que pediu (por exemplo, escrever num arquivo "só leitura").
  • Is a directory: o nome é de uma pasta, e não de um arquivo.
  • Too many open files: o programa abriu arquivos demais sem fechar (seção 5).

3. Os modos de abertura

O segundo argumento do fopen diz o que você quer fazer com o arquivo. Os três principais são "r", "w" e "a", e a diferença entre "w" e "a" é a que mais pega iniciante. Veja dois programas iguais, que só mudam o modo:

#include <stdio.h>

int main(void) {
    FILE *arq = fopen("diario.txt", "w");
    if (arq == NULL) {
        perror("diario.txt");
        return 1;
    }
    fputs("Abri a loja às 8h.\n", arq);
    fclose(arq);
    return 0;
}

O modo_a.c é a mesma coisa, com "a" (de append, acrescentar) no lugar do "w". A Ana rodou cada um duas vezes:

$ gcc -std=c23 -Wall -Wextra modo_w.c -o modo_w
$ gcc -std=c23 -Wall -Wextra modo_a.c -o modo_a
$ ./modo_w
$ ./modo_w
$ cat diario.txt
Abri a loja às 8h.                « duas vezes "w": só uma linha
$ ./modo_a
$ ./modo_a
$ cat diario.txt
Abri a loja às 8h.
Abri a loja às 8h.
Abri a loja às 8h.                « mais duas com "a": acrescentou no fim
$ ./abrir2
Primeira linha: Abri a loja às 8h.

O "w" apaga o arquivo inteiro na hora em que abre, antes mesmo de você escrever qualquer coisa. Rodar duas vezes deixou uma linha só. O "a" mantém o que já existia e escreve sempre no fim. E agora que o diario.txt existe, o abrir2 consegue ler a primeira linha.

ModoParaSe o arquivo existeSe não existe
"r"LerLê do começoFalha (NULL)
"w"EscreverApaga tudo e começa vazioCria
"a"AcrescentarMantém e escreve no fimCria
"r+"Ler e escreverMantém; começa no inícioFalha (NULL)
"w+"Escrever e lerApaga tudoCria
"a+"Ler e acrescentarMantém; a escrita vai sempre para o fimCria
"wx" C11Criar um arquivo novoFalha (NULL): não apaga nadaCria
…com b: "rb", "wb", "ab", "r+b"Arquivo binárioIgual ao modo sem b; veja Arquivos Binários

Os modos com + leem e escrevem no mesmo arquivo; para isso é preciso mudar de posição no meio do caminho, e a aula fseek e ftell mostra como. No dia a dia, você vai usar quase sempre os três primeiros.

Missão: a caixa tem duas abas: o programa lista.c e o arquivo lista.txt, com três itens. O programa deveria acrescentar leite e mostrar a lista inteira, mas rode e veja: os três itens antigos sumiram. Troque o modo do primeiro fopen para que a lista mostre arroz, feijão, café e leite.

#include <stdio.h>

int main(void) {
    /* 1) acrescenta um item à lista */
    FILE *arq = fopen("lista.txt", "w");
    if (arq == NULL) {
        perror("lista.txt");
        return 1;
    }
    fputs("leite\n", arq);
    fclose(arq);

    /* 2) mostra a lista inteira */
    arq = fopen("lista.txt", "r");
    if (arq == NULL) {
        perror("lista.txt");
        return 1;
    }
    char linha[100];
    while (fgets(linha, sizeof linha, arq) != NULL) {
        printf("- %s", linha);
    }
    fclose(arq);
    return 0;
}
arroz
feijão
café

#include <stdio.h>

int main(void) {
    /* 1) acrescenta um item à lista */
    FILE *arq = fopen("lista.txt", "a");
    if (arq == NULL) {
        perror("lista.txt");
        return 1;
    }
    fputs("leite\n", arq);
    fclose(arq);

    /* 2) mostra a lista inteira */
    arq = fopen("lista.txt", "r");
    if (arq == NULL) {
        perror("lista.txt");
        return 1;
    }
    char linha[100];
    while (fgets(linha, sizeof linha, arq) != NULL) {
        printf("- %s", linha);
    }
    fclose(arq);
    return 0;
}

A linha em branco no fim da aba lista.txt é o \n que termina o café: sem ela, o leite ficaria grudado (caféleite). No editor, a pasta dos arquivos volta ao estado das abas a cada Rodar: por isso o leite aparece uma vez só, mesmo se você rodar várias vezes. No seu computador, cada execução acrescentaria mais um.

C11 "wx": criar sem apagar

O "w" apaga sem perguntar. Às vezes é exatamente o que você não quer: um programa que cria o arquivo de configuração na primeira vez não deveria destruir a configuração que o usuário já ajustou. O x (de exclusive, exclusivo) junto do w faz o fopen falhar se o arquivo já existe:

#include <stdio.h>

int main(void) {
    FILE *arq = fopen("config.txt", "wx");
    if (arq == NULL) {
        perror("config.txt");
        return 1;
    }
    fputs("volume=5\n", arq);
    fclose(arq);
    printf("Criei config.txt\n");
    return 0;
}
$ gcc -std=c23 -Wall -Wextra modo_x.c -o modo_x
$ ./modo_x
Criei config.txt
$ ./modo_x
config.txt: File exists           « na segunda vez, não apagou nada
$ cat config.txt
volume=5

File exists = "o arquivo existe". O x só vale junto de w: "wx", "wbx" ou "w+x". No editor do guia funciona igual.


4. Em que pasta o arquivo fica?

Um nome sozinho, como "recado.txt", é um caminho relativo: ele é procurado (ou criado) na pasta de trabalho, que é a pasta em que você estava no terminal quando rodou o programa. Não é a pasta do .c, nem a do executável. Veja a Ana rodando o mesmo recado de outra pasta:

$ cd ..                      « sobe para /home/ana
$ c/recado                   « o programa continua em ~/c
Recado gravado!
$ ls recado.txt c/recado.txt
c/recado.txt
recado.txt                   « um recado novo, criado em /home/ana

O recado.txt foi criado em /home/ana, onde a Ana estava, e não em /home/ana/c, onde o programa mora. Esse é o motivo número um do "mas o arquivo está ali!": o programa está procurando em outra pasta. Acontece muito em IDEs como VS Code e Code::Blocks, que rodam o programa a partir de uma pasta diferente da do código.

Para não depender da pasta de trabalho, use um caminho absoluto, que começa com / e diz o endereço completo. Este programa testa quatro jeitos de chegar ao mesmo arquivo:

#include <stdio.h>

void tentar(const char *caminho) {
    FILE *arq = fopen(caminho, "r");
    if (arq == NULL) {
        perror(caminho);
        return;
    }
    printf("%s: abriu!\n", caminho);
    fclose(arq);
}

int main(void) {
    tentar("recado.txt");
    tentar("/home/ana/c/recado.txt");
    tentar("~/c/recado.txt");
    tentar("../c/recado.txt");
    return 0;
}
$ gcc -std=c23 -Wall -Wextra caminho.c -o caminho
$ ./caminho
recado.txt: abriu!
/home/ana/c/recado.txt: abriu!
~/c/recado.txt: No such file or directory
../c/recado.txt: abriu!
$ cd ~
$ c/caminho
recado.txt: No such file or directory
/home/ana/c/recado.txt: abriu!
~/c/recado.txt: No such file or directory
../c/recado.txt: No such file or directory
CaminhoO que significaResultado
"recado.txt"Relativo: na pasta de trabalhoAbriu em ~/c; de ~, não achou.
"/home/ana/c/recado.txt"Absoluto: o endereço completoAbriu das duas pastas.
"~/c/recado.txt"O ~ é um atalho do terminal, não do CNunca abre: o fopen procura uma pasta chamada ~.
"../c/recado.txt"Relativo: .. é a pasta de cimaDe ~/c, sobe para ~ e entra em c: abriu. De ~, procurou /home/c.

No editor do guia, a pasta de trabalho é /home/ana, e é lá que ficam as abas que não são .c.

E no Windows?

No Windows, os caminhos usam a barra invertida: C:\Users\ana\notas.txt. Mas dentro de uma string em C, a \ começa um escape (como \n), então escreva "C:\\Users\\ana\\notas.txt" ou, mais simples, use a barra normal, que o Windows também aceita: "C:/Users/ana/notas.txt". O erro de esquecer está em "Quando dá errado".


5. Por que fechar: fclose

O programa terminou e o arquivo ficou lá: então para que o fclose? Por dois motivos.

Motivo 1: o que ainda não foi gravado

O fputs não escreve no disco a cada chamada. Ele junta o texto numa área de espera na memória, o buffer, e só manda para o disco de vez em quando, em blocos (é muito mais rápido assim). O fclose manda o que sobrou no buffer. Quando o programa termina normalmente (com return no main ou exit), o C fecha sozinho os arquivos esquecidos, mas se o programa morre no meio (uma falha de segmentação, por exemplo), o que estava no buffer se perde. A aula Escrever Arquivos mostra isso acontecendo e o fflush, que esvazia o buffer sem fechar.

Motivo 2: o sistema tem um limite de arquivos abertos

Cada arquivo aberto ocupa um lugar numa lista do sistema, e essa lista tem tamanho. Este programa abre o mesmo arquivo sem parar, sem nunca fechar:

#include <stdio.h>

int main(void) {
    int abertos = 0;
    while (1) {
        FILE *arq = fopen("recado.txt", "r");
        if (arq == NULL) {
            perror("fopen");
            break;
        }
        abertos++;          /* abre e nunca fecha */
    }
    printf("Consegui abrir %d vezes.\n", abertos);
    return 0;
}
$ ulimit -n                       « o limite deste terminal
1024
$ gcc -std=c23 -Wall -Wextra abertos.c -o abertos
$ ./abertos
fopen: Too many open files
Consegui abrir 1021 vezes.

Too many open files = "arquivos abertos demais". O limite era 1024, e três lugares já estavam ocupados desde o começo: o teclado e a tela (veja o quadro abaixo). Num programa pequeno, você nunca vai chegar perto. Num programa que roda o dia inteiro e abre um arquivo a cada pedido da lojinha, esquecer o fclose é questão de tempo para tudo parar. No editor do guia não há esse limite: com o recado.txt numa aba, o programa acima ficaria abrindo sem parar até o editor interrompê-lo, depois de 15 segundos.

Regra prática: escreveu um fopen, escreva logo o fclose correspondente, e só depois preencha o meio. Em todos os caminhos de saída: se uma função tem um return no meio, o arquivo precisa ser fechado antes dele também.
Três arquivos que já nascem abertos

Todo programa C começa com três FILE * prontos, declarados no stdio.h: stdin (a entrada, normalmente o teclado), stdout (a saída, a tela) e stderr (a saída de erros, também a tela, que é onde o perror escreve). Por isso fputs("Olá\n", stdout); faz o mesmo que printf("Olá\n");, e o fgets(linha, 100, stdin) lê do teclado. Você não abre nem fecha esses três (aula stdin, stdout e stderr).


Formas de abrir para escrever: qual usar

Você quer…ModoExemplo
Ler um arquivo que deve existir"r"Carregar a lista de compras.
Gerar o arquivo do zero a cada vez"w"O relatório de vendas do dia, refeito toda noite.
Ir juntando linhas, sem perder as antigas"a"Um diário, um registro (log) de pedidos.
Criar só se ainda não existir"wx" C11A configuração padrão na primeira vez.
Mudar um pedaço no meio do arquivo"r+" (ou "r+b")Corrigir a nota de um aluno (aula fseek).
Guardar os bytes da memória"wb" / "rb"O "save" de um jogo (aula Arquivos Binários).

Quando dá errado

Falha de segmentação: não conferiu o NULL
#include <stdio.h>

int main(void) {
    FILE *arq = fopen("diario.txt", "r");
    char linha[100];
    fgets(linha, sizeof linha, arq);
    printf("%s", linha);
    fclose(arq);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra semteste.c -o semteste
$ rm diario.txt
$ ./semteste
Falha de segmentação     (imagem do núcleo gravada) ./semteste

O que aconteceu: sem o diario.txt, o fopen devolveu NULL, e o fgets tentou ler de um ponteiro nulo. O compilador não avisa nada: o erro só aparece ao rodar, e só no dia em que o arquivo faltar. No editor do guia, a mesma falha termina com o código 139.

Como resolver: todo fopen é seguido de if (arq == NULL) { perror(…); return 1; }, sem exceção.

"No such file or directory" ao criar um arquivo numa pasta
$ ./pasta
relatorios/maio.txt: No such file or directory
$ mkdir relatorios
$ ./pasta
Relatório criado.

O que aconteceu: o programa fez fopen("relatorios/maio.txt", "w"). O "w" cria o arquivo, mas não cria pastas: se relatorios não existe, falha.

Como resolver: crie a pasta antes (no terminal, com mkdir), ou grave na pasta de trabalho. O C padrão não tem uma função para criar pastas; no Linux existe a mkdir do <sys/stat.h>, que não é assunto desta aula.

"Permission denied"
$ chmod 444 diario.txt           « deixa o arquivo "só leitura"
$ ./modo_w
diario.txt: Permission denied
$ chmod 644 diario.txt           « devolve a permissão de escrita

O que aconteceu: "permissão negada". O arquivo existe, mas está marcado como só leitura, e o "w" pediu para escrever. O mesmo acontece ao tentar criar arquivos em pastas do sistema, como /etc.

Como resolver: grave numa pasta sua (dentro de /home/ana), ou confira as permissões com ls -l.

"passing argument 2 of ‘fopen’ makes pointer from integer without a cast": aspas simples no modo
$ gcc -std=c23 -Wall -Wextra aspas.c -o aspas
aspas.c: In function ‘main’:
aspas.c:4:37: error: passing argument 2 of ‘fopen’ makes pointer from integer without a cast [-Wint-conversion]
    4 |     FILE *arq = fopen("recado.txt", 'r');
      |                                     ^~~
      |                                     |
      |                                     int
In file included from aspas.c:1:
/usr/include/stdio.h:272:44: note: expected ‘const char * restrict’ but argument is of type ‘int’
  272 |                     const char *__restrict __modes)
      |                     ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~

O que aconteceu: 'r', com aspas simples, é um caractere (um número, para o C; veja Caracteres). O modo precisa ser um texto, com aspas duplas. A nota mostra o que o fopen esperava: const char *, um texto. O Clang do editor diz incompatible integer to pointer conversion passing 'int' to parameter of type 'const char *'.

Como resolver: fopen("recado.txt", "r"), com aspas duplas.

"incomplete universal character name \U": caminho do Windows
$ gcc -std=c23 -Wall -Wextra windows.c -o windows
windows.c: In function ‘main’:
windows.c:4:47: error: incomplete universal character name \U
    4 |     FILE *arq = fopen("C:\Users\ana\notas.txt", "r");
      |                                               ^

O que aconteceu: numa string, \U começa um escape que espera o código de um caractere Unicode, e sers não é um código. O \a e o \n do caminho também seriam escapes (um bipe e uma quebra de linha!). O Clang do editor diz \U used with no following hex digits.

Como resolver: dobre as barras ("C:\\Users\\ana\\notas.txt") ou use a barra normal ("C:/Users/ana/notas.txt").

"pointer ‘arq’ used after ‘fclose’" e double free: fechou duas vezes
$ gcc -std=c23 -Wall -Wextra fecha2.c -o fecha2
fecha2.c: In function ‘main’:
fecha2.c:11:5: warning: pointer ‘arq’ used after ‘fclose’ [-Wuse-after-free]
   11 |     fclose(arq);
      |     ^~~~~~~~~~~
fecha2.c:9:5: note: call to ‘fclose’ here
    9 |     fclose(arq);
      |     ^~~~~~~~~~~
$ ./fecha2
Fechei uma vez.
free(): double free detected in tcache 2
Abortado                   (imagem do núcleo gravada) ./fecha2

O que aconteceu: o fclose devolve ao sistema a memória do FILE. O segundo fclose tentou devolver de novo uma memória que já não era do programa (double free, "liberar duas vezes"; veja malloc e free), e a biblioteca do Linux abortou o programa. O gcc até avisou na compilação: "ponteiro usado depois do fclose". No editor do guia, o segundo fclose passa calado e o programa termina normalmente, o que não quer dizer que esteja certo.

Como resolver: feche cada arquivo uma única vez. Se o mesmo ponteiro pode ser fechado em lugares diferentes, faça arq = NULL; depois do fclose e confira antes de fechar.


Referência rápida

EscritaO que faz
FILE *arq;Uma variável para um arquivo aberto.
arq = fopen("nome.txt", "r");Abre para ler; NULL se não conseguir.
fopen(nome, "w")Cria, ou apaga e recomeça, para escrever.
fopen(nome, "a")Abre para acrescentar no fim; cria se não existir.
fopen(nome, "wx")C11: cria só se não existir.
if (arq == NULL) { perror(nome); return 1; }A conferência obrigatória depois de todo fopen.
fclose(arq);Grava o que falta e fecha. Uma vez só por arquivo.
"recado.txt" / "/home/ana/c/recado.txt"Caminho relativo (pasta de trabalho) / absoluto.
stdin, stdout, stderrOs três FILE * que já nascem abertos.

Exercícios

1. O tipo

Complete a declaração da variável que guarda um arquivo aberto:

*arq = fopen("notas.txt", "r");

2. O modo do diário

Complete o modo para acrescentar uma linha no fim do diário, sem apagar as anteriores:

FILE *arq = fopen("diario.txt", "");

3. A conferência

Complete a condição que descobre se o fopen falhou:

if () { perror("notas.txt"); return 1; }

4. O arquivo vendas.txt tem 50 linhas. O que sobra dele depois de fopen("vendas.txt", "w") seguido de fclose, sem escrever nada?

5. A Ana está em /home/ana e roda c/recado, que faz fopen("recado.txt", "w"). Onde o arquivo é criado?

6. Por que fopen("~/notas.txt", "r") devolve NULL, mesmo com o arquivo em /home/ana/notas.txt?

Missão final: gravar e ler o recado

Missão: escreva os dois fopen que faltam. O primeiro abre recado.txt para escrever; o segundo abre o mesmo arquivo para ler. Confira o NULL depois de cada um (com perror e return 1) e feche o arquivo depois de cada parte. A saída deve ser Li do arquivo: Comprar pão e leite.

#include <stdio.h>

int main(void) {
    FILE *arq;

    /* 1) abra recado.txt para escrever e confira o NULL */

    fputs("Comprar pão e leite.\n", arq);
    /* feche o arquivo */

    /* 2) abra recado.txt para ler e confira o NULL */

    char linha[100];
    if (fgets(linha, sizeof linha, arq) != NULL) {
        printf("Li do arquivo: %s", linha);
    }
    /* feche o arquivo */
    return 0;
}
#include <stdio.h>

int main(void) {
    FILE *arq;

    /* 1) abra recado.txt para escrever e confira o NULL */
    arq = fopen("recado.txt", "w");
    if (arq == NULL) {
        perror("recado.txt");
        return 1;
    }
    fputs("Comprar pão e leite.\n", arq);
    fclose(arq);

    /* 2) abra recado.txt para ler e confira o NULL */
    arq = fopen("recado.txt", "r");
    if (arq == NULL) {
        perror("recado.txt");
        return 1;
    }
    char linha[100];
    if (fgets(linha, sizeof linha, arq) != NULL) {
        printf("Li do arquivo: %s", linha);
    }
    fclose(arq);
    return 0;
}

Rode o código inicial antes de mexer: o compilador avisa que arq é usado sem valor, e o programa cai. É o mesmo problema do NULL não conferido, só que pior: ali nem houve um fopen.


Resumo

  • Arquivos guardam informação depois que o programa termina. Todo trabalho começa com fopen e termina com fclose.
  • FILE *arq = fopen(nome, modo); devolve NULL quando não consegue abrir. Confira sempre, e use perror para mostrar o motivo.
  • "r" lê (o arquivo precisa existir), "w" apaga e escreve, "a" acrescenta no fim, "wx" (C11) só cria se não existir. O + permite ler e escrever; o b é para binário.
  • Um nome sem / no começo é procurado na pasta de trabalho, a do terminal. O ~ não funciona no fopen.
  • fclose grava o que ficou no buffer e libera o lugar do arquivo; feche cada arquivo uma vez só.
  • stdin, stdout e stderr são arquivos que já nascem abertos.