I/O de Arquivos em C: Streams, Funções e Manipulação

Classificado em Computação

Escrito em em português com um tamanho de 15,22 KB

E/S (I/O) com Arquivos na Linguagem C

A linguagem C não possui comandos de E/S nativos. Todas as operações de E/S ocorrem mediante chamadas a funções da biblioteca padrão de C. Essa abordagem torna o sistema de arquivos de C extremamente poderoso e flexível. O sistema de E/S de C é único porque os dados podem ser transferidos na sua representação binária ou em um formato de texto legível por humanos, o que facilita a criação de arquivos que satisfaçam qualquer necessidade.

Streams e Arquivos

O sistema de E/S de C fornece uma interface consistente ao programador, independente do dispositivo real que é acessado. Com isso, o sistema de E/S de C provê um nível de abstração entre o programador e o dispositivo utilizado. Essa abstração é chamada de stream e o dispositivo real é chamado de arquivo.

Exemplos de Dispositivos Associados a Streams

  • Scn (Scanner): Entrada/Saída
  • Stdin (Teclado): Entrada
  • Net (Placa de Rede): Entrada/Saída
  • Fil (Arquivo): Entrada/Saída
  • Print (Impressora): Saída
  • Stdout (Monitor): Saída

O Conceito de Stream

O sistema de arquivos de C é projetado para trabalhar com uma ampla variedade de dispositivos (terminais, acionadores de disco e fita). Embora cada dispositivo seja muito diferente, o sistema de arquivo com buffer os transforma em um dispositivo lógico chamado STREAM. Todas as streams se comportam de forma semelhante e são amplamente independentes do dispositivo. A mesma função pode escrever em um arquivo em disco ou em algum outro dispositivo. Existem dois tipos de stream:

  • Streams de Texto: Uma sequência de caracteres organizada em linhas (terminadas pelo caractere de nova linha).
  • Streams Binárias: Uma sequência de bytes, sem tradução de caracteres.

O Ponteiro de Arquivo

O ponteiro de arquivo identifica um arquivo específico em disco e é usado pela stream associada para direcionar as funções de E/S. Um ponteiro de arquivo é uma variável ponteiro do tipo FILE (definida em stdio.h). Para ler ou escrever arquivos, seu programa precisa obter essa variável ponteiro, geralmente declarada como:

FILE *fp;

Arquivos

Em C, um arquivo pode ser qualquer coisa, desde um arquivo salvo em disco até um terminal ou impressora. Você associa uma stream a um arquivo específico realizando uma operação de abertura. Uma vez que o arquivo é aberto, informações podem ser trocadas entre ele e o seu programa.

Nem todos os arquivos apresentam os mesmos recursos. Por exemplo, um arquivo em disco pode suportar acesso aleatório, enquanto um teclado não. Todas as streams são iguais, mas nem todos os arquivos. Se o arquivo suporta acesso aleatório, a abertura inicializa o indicador de posição para o começo do arquivo. Quando um caractere é lido ou escrito, o indicador de posição é incrementado, garantindo a progressão através do arquivo.

Um arquivo é desassociado de uma stream específica através de uma operação de fechamento. Se um arquivo de saída for fechado, o conteúdo (se houver) de sua stream será escrito no dispositivo externo. Esse processo é conhecido como FLUSHING. Cada stream associada a um arquivo tem uma estrutura de controle do tipo FILE, que é definida em STDIO.H.

Funções Comuns do Sistema de Arquivos de C

O sistema de arquivos de C é composto de diversas funções inter-relacionadas. As mais comuns são:

fopen
Abre um arquivo.
fclose
Fecha um arquivo.
putc / fputc
Escreve um caractere em um arquivo.
getc / fgetc
Lê um caractere de um arquivo.
fseek
Posiciona o arquivo em um byte específico (acesso aleatório).
fprintf
Similar ao printf, mas para arquivo.
fscanf
Similar ao scanf, mas para arquivo.
feof
Devolve verdadeiro se for o fim do arquivo (End Of File).
ferror
Devolve verdadeiro se ocorreu um erro.
rewind
Recoloca o indicador de posição no início do arquivo.
remove
Apaga um arquivo.
fflush
Descarrega (flush) o buffer de um arquivo.

Abrindo um Arquivo: fopen()

A função fopen() abre uma stream para uso e associa um arquivo a ela. Ela retorna um ponteiro de arquivo (do tipo FILE*) associado a esse arquivo. Mais frequentemente, o arquivo é um arquivo em disco.

Fechando um Arquivo: fclose()

A função fclose() fecha um arquivo através da stream que foi aberta por meio da chamada de fopen(). Ela escreve qualquer dado que ainda esteja no buffer de disco e então fecha o arquivo a nível de sistema operacional.

int fclose(FILE *fp);

O parâmetro fp é o ponteiro para o arquivo. fclose() retorna zero se a operação foi bem-sucedida; caso contrário, retorna um valor diferente de zero (Ex: "disco cheio ou ausente").

Escrevendo um Caractere: putc() ou fputc()

A função putc() escreve um caractere em um arquivo que foi previamente aberto para escrita por meio da função fopen().

int putc(int ch, FILE *fp);
  • fp: Ponteiro para o arquivo.
  • ch: Caractere a ser escrito.

Se a operação putc() for bem-sucedida, ela devolverá o caractere escrito; caso contrário, devolverá EOF.

Lendo um Caractere: getc() ou fgetc()

A função getc() lê um caractere de um arquivo que foi previamente aberto para leitura por meio da função fopen().

int getc(FILE *fp);
  • fp: Ponteiro para o arquivo.

A função retorna o caractere lido ou EOF caso o final do arquivo tenha sido atingido ou algum erro tenha ocorrido.

*Lê todos os caracteres do arquivo até chegar ao fim.

Reposicionamento: rewind()

A função rewind() reposiciona o indicador de posições do arquivo no início do mesmo (ela "rebobina" o arquivo).

void rewind(FILE *fp);

Verificação de Erro: ferror()

A função ferror() determina se uma operação produziu um erro.

int ferror (FILE *fp);

Retorna verdadeiro (diferente de zero) se ocorreu erro durante a última operação no arquivo; caso contrário, retorna falso (zero).

Modos de Abertura de Arquivo

ModoSignificado
rArquivo de texto para leitura.
wArquivo de texto para escrita (substitui o arquivo se existir, ou cria um novo).
aAnexa a um arquivo de texto.
rbArquivo binário para leitura.
wbArquivo binário para escrita (substitui ou cria).
abAnexa a um arquivo binário.
r+Arquivo de texto para leitura e escrita.
w+Arquivo de texto para leitura e escrita (substitui ou cria).
a+Anexa com leitura e escrita (texto).
r+bArquivo binário para leitura e escrita.
w+bArquivo binário para leitura e escrita (substitui ou cria).
a+bAnexa com leitura e escrita (binário).

Nota: O modo w (e variações como w+, wb, w+b) substitui o arquivo se ele já existir. Se não houver arquivo, ele é criado.

Fim de Arquivo: feof()

Quando um arquivo é aberto para leitura binária, um valor igual a EOF pode ser lido, o que poderia fazer com que a rotina de arquivo parasse, mesmo que o final físico do arquivo não tenha sido alcançado. Para resolver esse problema, C inclui a função feof(), que determina quando o final de arquivo foi atingido na leitura de dados binários. Também é aplicável para arquivos de texto.

int feof (FILE *fp);

Devolve verdadeiro (diferente de zero) se o final do arquivo foi atingido. Caso contrário, devolve Falso (0).

Exemplo de uso:

while(!feof(fp)) {
ch = getc(fp);
}

Leitura e Escrita de Strings: fputs() e fgets()

Além de getc() e putc(), C suporta as funções fgets() e fputs(), que efetuam operações de leitura e gravação de strings de caracteres em arquivos em disco. Essas funções operam de maneira muito semelhante a putc() e getc(), mas trabalham com strings em vez de um único caractere.

int fputs(const char *str, FILE *fp);
char* fgets(char *str, int length, FILE *fp);

A função fgets() lê uma string da stream especificada até que um caractere de nova linha (\n) seja lido, ou length - 1 caracteres tenham sido lidos.

Removendo Arquivos: remove()

A função remove() apaga o arquivo especificado.

int remove(const char* filename);

Devolve zero caso seja bem-sucedida, ou um valor diferente de zero, caso contrário.

Exemplo:

if (remove("teste.txt")) {
printf("O arquivo não pode ser apagado");
exit(1);
}

Descarregando o Buffer: fflush()

Para esvaziar o conteúdo de uma stream de saída, deve-se utilizar a função fflush().

int fflush(FILE *fp);

Essa função escreve o conteúdo de qualquer dado existente no buffer para o arquivo associado a fp. Se fflush() for chamada com o valor NULL, todos os arquivos abertos para saída serão descarregados. Devolve 0 para indicar sucesso; caso contrário, devolve EOF.

E/S de Bloco: fread() e fwrite()

Para ler e escrever tipos de dados maiores que um byte (como estruturas), o sistema de arquivos de C fornece duas funções: fread() e fwrite(). Essas funções permitem a leitura e escrita de blocos de qualquer tipo de dado.

size_t fread(void *buffer, size_t num_bytes, size_t count, FILE *fp);
size_t fwrite(const void *buffer, size_t num_bytes, size_t count, FILE *fp);

Parâmetros

Parâmetrofread()fwrite()
bufferPonteiro para a região de memória que receberá os dados.Ponteiro para os dados que serão escritos no arquivo.
num_bytesNúmero de bytes a serem lidos (tamanho de cada item).Número de bytes a serem escritos (tamanho de cada item).
countDetermina quantos itens (blocos de num_bytes) serão lidos ou escritos.
fpPonteiro para a stream do arquivo.

A função fread() devolve o número de itens lidos. Esse valor poderá ser menor que count se o final do arquivo for atingido ou ocorrer um erro. A função fwrite() devolve o número de itens escritos, a menos que ocorra um erro.

Exemplo de uso com estrutura:

typedef struct {
int ra;
char nome[101];
} aluno;

aluno x;
x.ra = 1234;
// x.nome = "Jose"; (A atribuição direta de string não é válida em C)

// Escrita
fwrite(&x, sizeof(aluno), 1, fp);

// Leitura
fread(&x, sizeof(aluno), 1, fp);

Acesso Aleatório: fseek()

Operações de leitura e escrita aleatórias podem ser executadas com a ajuda de fseek(), que modifica o indicador de posição do arquivo.

int fseek(FILE *fp, long num_bytes, int origin);
Origin (Constante)Posição
SEEK_SETInício do arquivo.
SEEK_CURPosição atual.
SEEK_ENDFinal do arquivo.

A função devolve 0 quando bem-sucedida e um valor diferente de zero se ocorrer um erro.

Exemplo: fseek(fp, 9 * sizeof(aluno), SEEK_SET);

Streams Padrão

Sempre que um programa em C começa a execução, três streams são abertas automaticamente:

  • A entrada padrão (stdin)
  • A saída padrão (stdout)
  • A saída de erro padrão (stderr)

Normalmente, essas streams referem-se ao console, mas podem ser redirecionadas pelo sistema operacional para algum outro dispositivo em ambientes que suportam redirecionamento de E/S.

A função putchar, por exemplo, é implementada usando putc e stdout:

int putchar (char c) {
return putc(c, stdout);
}

Exemplo de Redirecionamento de Saída (SO):

C:\>teste.exe > saida.txt

Arquivos de Registro e Buffering

Um arquivo de registro é uma coleção de registros mantida em disco. A grande vantagem no uso de arquivos é que os dados armazenados não são perdidos quando o programa termina sua execução (diferentemente do que ocorre com vetores, que são mantidos na memória principal).

A desvantagem é que o acesso a disco é muito mais demorado do que o acesso à memória principal e, consequentemente, o uso de arquivos torna a execução do programa mais lenta.

Para um acesso a disco mais eficiente e seguro, os sistemas operacionais modernos usam uma área da memória principal (buffer) para controlar a transferência de dados do/para o disco. O uso do buffer permite diminuir o número de acessos a disco, aumentando a eficiência do programa (além da proteção dos dados devido ao isolamento provido). Para usar um arquivo, é necessário ter acesso ao seu buffer.

Entradas relacionadas: