cblc/aulas/14-rfiles/README.org
2025-05-05 10:32:30 -03:00

6.9 KiB
Raw Permalink Blame History

Curso Básico da Linguagem C

Aula 14: Abertura de arquivos para leitura

Vídeo desta aula

Em sistemas Unix-like, um arquivo é qualquer abstração de recursos de entrada e saída representada como fluxos de dados (streams) acessados por meio de operações padronizadas, independentemente de seu conteúdo ou finalidade.

Neste sentido, existem sete tipos de arquivos:

  • Diretórios
  • Arquivos regulares (ou comuns)
  • Ligações simbólicas
  • Dispositivos especiais caractere
  • Dispositivos especiais de bloco
  • Arquivos de pipe (ou FIFO)
  • Sockets

Quando queremos acessar um arquivo, seja de que tipo for, nós precisamos que o sistema disponibilize vários recursos, como:

  • Um descritor de arquivo (file descriptor): um número inteiro que identifica de forma única o arquivo aberto no contexto do processo.
  • Uma tabela de descritores do processo: uma estrutura que associa os descritores de arquivos a entradas na tabela global de arquivos abertos.
  • A tabela global de arquivos abertos (file table): que armazena informações como a posição atual de leitura/gravação e as flags de acesso.
  • A tabela de inodes: que contém os metadados do arquivo, como permissões, o "dono" do arquivo, o tipo do arquivo e o ponteiro para os dados no disco.
  • Os buffers do sistema: usados para otimizar acesso ao conteúdo do arquivo acumulando dados temporariamente na memória.

Na linguagem C, tudo isso é abstraído como o tipo FILE *, que é um ponteiro para a estrutura de dados (uma struct) disponibilizada para cada operação de acesso ao fluxo de dados (stream) que irá representar o arquivo.

Resumindo:

  • No sistema, arquivos são abstrações de recursos de entrada e saída representados como fluxos de dados;
  • Na linguagem C, os fluxos de dados, bem como os recursos que eles representam, são abstraídos com o tipo FILE * (ponteiro para FILE).

O tipo FILE * é definido na biblioteca padrão (cabeçalho stdio.h).

Diferença entre streams e descritores de arquivos

Descritores de arquivos (tipo int)

  • São expressos como inteiros (tipo int).
  • Visíveis em /proc/PID/fd.
  • Identificam arquivos em chamadas de sistema (open, close, read, write, etc).
  • Representam diretamente uma entrada na tabela de arquivos abertos do processo.

Streams (tipo ponteiro para FILE)

  • São abstrações de todos os recursos abertos para operações com arquivos.
  • Utilizados em funções da biblioteca padrão (fopen, fclose, fgets, fprintf, etc).
  • Utilizam descritores de arquivos para interagir com o sistema.
  • Envolvem acumulação (buffering) na memória do processo.

Procedimento geral de acesso a arquivos para leitura

Geralmente, as operações com arquivos envolvem três etapas: abertura, processamento e fechamento.

Abertura

  • Disponibilização dos recursos para acesso ao arquivo.
  • Definição do tipo de operação (escrita, append e/ou leitura).
  • Com a chamada de sistema open, recebemos um descritor de arquivos.
  • Com a função fopen, recebemos um ponteiro para FILE (stream).
  • Com fopen, um ponteiro nulo (NULL) será retornado no caso de erros.
  • Com open, o retorno será -1 no caso de erros.
  • Com ambas as funções, a variável errno receberá a identificação do erro.

Neste curso, nós utilizaremos apenas a função fopen.

Exemplo:

char *file = "arquivo.txt";
FILE *stream = fopen(file, "r");

Aqui, o arquivo arquivo.txt, se existir no diretório corrente, será aberto para leitura (modo de abertura "r"). Se tudo correr bem, nós teremos o fluxo de dados stream para manipular o arquivo.

Para tratar eventuais erros, nós podemos fazer algo assim:

char *file = "arquivo.txt";
FILE *stream = fopen(file, "r");
if (!stream) {
    perror("Erro ao abrir o arquivo");
    return EXIT_FAILURE;
 }

A função perror imprime, na saída padrão de erros (stderr), a descrição do último erro atribuído à variável errno. A string, passada como argumento, será utilizada como prefixo da mensagem impressa.

Processamento

É quando nós realizamos alguma operação com o arquivo, como ler seu conteúdo, escrever algo nele, obter seu tamanho em bytes, reposicionar seu ponteiro interno, etc. Por exemplo, nós podemos abrir o arquivo /etc/shells para ler e imprimir todo seu conteúdo:

char *file = "/etc/shells";

// Abertura do arquivo...
FILE *stream = fopen(file, "r");
if (!stream) {
    perror("Erro ao abrir o arquivo");
    return EXIT_FAILURE;
}

// Processamento...
char line[BUFSIZ];   // Buffer para acumular cada linha lida
while (fgets(line, BUFSIZ, stream)) {
    printf("%s", line);
}

Aqui, fgets lerá o conteúdo do arquivo associado a stream (/etc/shells) até encontrar uma quebra de linha ou chagar a BUFSIZ-1 bytes.

A macro BUFSIZ é definida em stdio.h e expande o valor 8192 (8kB).

De um modo ou de outro, a estrutura de dados associada a stream registrará a quantidade de bytes que já foram lidos para que, no caso de um acesso subsequente, a leitura continue a partir do byte seguinte isso é o que chamamos de ponteiro interno do arquivo.

O ponteiro interno do arquivo pode ser obtido e manipulado com as funções fseek, ftell e rewind.

Consequentemente, enquanto fgets não retornar NULL (no caso de um erro ou do fim do arquivo), o conteúdo do buffer line será impresso e o arquivo será lido novamente a partir da nova posição de seu ponteiro interno.

Fechamento

  • Liberação dos recursos disponibilizados na abertura.
  • Tratamento adequado para os dados pendentes.
  • Possibilita a verificação de erros de fechamento.
  • Com close ou fclose, o retorno será 0 em caso de sucesso.
  • Com close, o retorno será -1 em caso de erro.
  • Com fclose, o retorno será EOF em caso de erro (que também tem valor -1).
  • Com ambas as funções, a variável errno receberá a identificação do erro.

Os arquivos abertos são fechados com o fim do processo, mas é sempre preferível fechá-los explicitamente para garantir a integridade dos dados e evitar vulnerabilidades (como o vazamento de recursos).

Para fechar o arquivo do nosso exemplo:

char *file = "/etc/shells";

// Abertura do arquivo...
FILE *stream = fopen(file, "r");
if (!stream) {
    perror("Erro ao abrir o arquivo");
    return EXIT_FAILURE;
}

// Processamento...
char line[BUFSIZ];   // Buffer para acumular cada linha lida
while (fgets(line, BUFSIZ, stream)) {
    printf("%s", line);
}

// Fechamento do arquivo...
fclose(stream);