.. | ||
README.org |
Curso Básico da Linguagem C
Aula 14: Abertura de arquivos para leitura
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 paraFILE
).
O tipo
FILE *
é definido na biblioteca padrão (cabeçalhostdio.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 paraFILE
(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ávelerrno
. 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 emstdio.h
e expande o valor8192
(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
erewind
.
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
oufclose
, 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);