Tudo o que um programa calcula desaparece quando ele termina, a menos que ele anote em algum lugar. A interface de arquivos do C vive em <stdio.h> - o mesmo cabeçalho do printf - e é deliberadamente parecida: um arquivo é um stream de bytes, e as funções que operam na tela têm gêmeas que recebem um stream como argumento.
O tipo do handle é FILE *. Você nunca olha dentro de um FILE; ele é um tipo opaco (veja typedef) e você só guarda um ponteiro para ele.
Os três passos
Toda operação com arquivo segue a mesma forma: abrir, usar, fechar.
A verificação de NULL não é paranoia opcional. O fopen falha sempre que o arquivo não existe (em modo de leitura), o diretório não é gravável, o caminho está errado, ou o processo ficou sem handles de arquivo - e cada um desses casos devolve NULL. Usar um handle NULL é uma falha de segmentação.
Mandar o erro para stderr em vez de stdout é a convenção: ele não é bufferizado e pode ser redirecionado separadamente da saída de verdade.
Modos do fopen
O segundo argumento é uma string curta de modo. Errá-la é o erro mais destrutivo desta área toda, porque "w" esvazia silenciosamente um arquivo existente.
| Modo | Lê | Escreve | Se o arquivo existe | Se não existe |
|---|---|---|---|---|
"r" | sim | não | abre no início | falha, devolve NULL |
"w" | não | sim | truncado até ficar vazio | criado |
"a" | não | sim | escreve só no fim | criado |
"r+" | sim | sim | abre no início | falha, devolve NULL |
"w+" | sim | sim | truncado até ficar vazio | criado |
"a+" | sim | sim | lê em qualquer lugar, escreve no fim | criado |
Acrescente um b a qualquer um deles ("rb", "wb", "ab+") para o modo binário - mais sobre isso no fim.
Duas regras que evitam perda real de dados:
- Use
"r"quando a intenção é ler."r+"em um nome de arquivo digitado errado falha de forma segura;"w+"cria um arquivo vazio e você não perdeu nada além de tempo."w"no nome certo quando você queria"r"destrói os dados. - Use
"a"para logs. Todofprintfcai no fim, independentemente de onde o stream estava posicionado, que é exatamente o que um log quer.
Escrever e ler em um único programa
O editor abaixo executa uma ida e volta completa - cria um arquivo, escreve registros nele, fecha, reabre para leitura e imprime o que encontra.
fprintf e fscanf são o printf e o scanf com um stream como primeiro argumento; tudo sobre seus especificadores de formato é idêntico, inclusive a largura %31s que impede nome de transbordar.
A condição do laço é == 2, o número de itens que o formato pede. Testar contra EOF em vez disso é um bug clássico: uma linha malformada faz o fscanf devolver 0, não EOF, e o laço gira para sempre sobre uma entrada que não consegue consumir.
Ler linha a linha com fgets
O fscanf é conveniente para dados rigidamente formatados. Para arquivos de texto - configuração, logs, CSV, qualquer coisa que um humano escreveu - leia linhas inteiras. Este laço é o canônico:
O que faz do fgets o padrão certo:
- Ele recebe o tamanho do buffer, então não pode transbordar. Passe
sizeof linhae a chamada continua correta se você redimensionar o array. - Ele devolve
NULLno fim do arquivo ou em caso de erro, o que dá uma condição de laço limpa. - Ele mantém a quebra de linha quando a linha coube no buffer. Isso é útil - nenhum
'\n'no que você recebeu significa que a linha era maior que o seu buffer e o resto ainda está esperando.strcspn(linha, "\n")encontra o índice da quebra de linha (ou o tamanho da string se não houver nenhuma), então atribuir'\0'ali a remove nos dois casos.
Para distinguir um fim de arquivo de verdade de um erro, pergunte depois do laço:
if (ferror(in)) {
fprintf(stderr, "erro de leitura\n");
} else if (feof(in)) {
/* fim normal */
}
Não escreva while (!feof(fp)) como condição de laço. O feof só se torna verdadeiro depois de uma leitura já ter falhado, então esse laço processa o conteúdo final do buffer uma vez a mais. Teste o valor de retorno da função de leitura - como os dois laços acima fazem.
Um caractere por vez: fgetc e fputc
Para trabalho no nível de bytes - contar caracteres, transformar um arquivo, copiar - fgetc e fputc lidam com um caractere por chamada.
Um detalhe que importa: c é declarado como int, não char. O fgetc devolve um int para poder devolver todo valor de byte possível e a sentinela distinta EOF (que é -1). Guardá-lo em um char faz a comparação contra EOF ser sempre falsa ou falsamente verdadeira para o byte 0xFF, dependendo de char ser com sinal na sua plataforma. Esta é uma das armadilhas mais antigas do C.
Verificar erros do jeito certo
Uma leitura de produção fica assim:
O errno guarda um código descrevendo a última falha e o strerror o transforma em uma frase; o perror imprime a sua mensagem mais essa frase em uma única chamada. Eles precisam de <errno.h> e <string.h>, respectivamente.
O fclose também pode falhar - ele esvazia os dados bufferizados, e a escrita pode não caber no disco - então, para qualquer coisa importante, verifique:
if (fclose(fp) != 0) {
fprintf(stderr, "falha ao esvaziar o buffer e fechar\n");
}
Modo binário em um parágrafo
O modo texto pode traduzir finais de linha (no Windows, \n vira \r\n na escrita e volta na leitura) e pode tratar certos bytes de forma especial. Para dados que não são texto - uma imagem, uma struct despejada literalmente, um blob comprimido - abra com b e use fread/fwrite, que movem bytes crus:
fwrite(ptr, tamanho, quantidade, fp) escreve quantidade itens de tamanho bytes e devolve quantos conseguiu; o fread é o espelho. Saiba que um arquivo escrito assim fica preso à máquina que o escreveu - preenchimento da struct, tamanho dos inteiros e ordem dos bytes todos vazam para os bytes - então serve para um cache ou um arquivo temporário e é errado para um formato que outros programas precisam ler.
Erros comuns
- Não verificar se o
fopendevolveuNULL. A falha que vem depois é atribuída à leitura, não à abertura. - Abrir com
"w"quando você queria"r". O arquivo é esvaziado antes de você perceber. - Esquecer o
fclose. A saída bufferizada se perde, e cada arquivo não fechado vaza um handle. while (!feof(fp)). Processa a última linha duas vezes. Teste a chamada de leitura.char c = fgetc(fp). Quebra a comparação comEOF. Useint.fscanf("%s", buf)sem largura. O mesmo estouro de buffer do scanf no teclado.- Caminhos relativos.
fopen("data.txt", "r")procura no diretório de trabalho, não ao lado do executável. Se um arquivo "some", geralmente é por isso.
Perguntas frequentes
Como abrir um arquivo em C?
FILE *fp = fopen("data.txt", "r"); abre um arquivo para leitura e devolve um handle FILE *, ou NULL se falhar. Sempre verifique se é NULL antes de usar o handle, e chame fclose(fp) quando terminar.
Quais são os modos do fopen em C?
"r" leitura (o arquivo precisa existir), "w" escrita (cria, ou trunca um arquivo existente deixando-o vazio), "a" append (cria, escreve no fim). Acrescentar + torna cada um de leitura e escrita: "r+", "w+", "a+". Acrescentar b ("rb", "wb") abre em modo binário.
Como ler um arquivo linha a linha em C?
Use fgets em um laço while: while (fgets(linha, sizeof linha, fp) != NULL) { ... }. Ele para em cada quebra de linha ou quando o buffer enche, devolve NULL no fim do arquivo, e não pode estourar porque você passa o tamanho do buffer.
Por que meu arquivo está vazio depois de escrever nele em C?
Na maioria das vezes você esqueceu o fclose. A saída é bufferizada, então os dados podem estar ainda na memória quando o programa termina de forma anormal. fclose esvazia o buffer e fecha; fflush(fp) esvazia sem fechar. A outra causa é abrir com "w" uma segunda vez, o que trunca o arquivo que você acabou de escrever.