conteúdo da aula 14

This commit is contained in:
Blau Araujo 2025-05-05 10:32:30 -03:00
parent fd25b0d1f0
commit 38d909ad07

200
aulas/14-rfiles/README.org Normal file
View file

@ -0,0 +1,200 @@
#+title: Curso Básico da Linguagem C
#+subtitle: Aula 14: Abertura de arquivos para leitura
#+author: Blau Araujo
#+startup: show2levels
#+options: toc:3
* Aula 14: Abertura de arquivos para leitura
[[https://www.youtube.com/watch?v=uh3UdYyzXRM][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=).
#+begin_quote
O tipo =FILE *= é definido na biblioteca padrão (cabeçalho =stdio.h=).
#+end_quote
** 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.
#+begin_quote
Neste curso, nós utilizaremos apenas a função =fopen=.
#+end_quote
Exemplo:
#+begin_src c
char *file = "arquivo.txt";
FILE *stream = fopen(file, "r");
#+end_src
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:
#+begin_src c
char *file = "arquivo.txt";
FILE *stream = fopen(file, "r");
if (!stream) {
perror("Erro ao abrir o arquivo");
return EXIT_FAILURE;
}
#+end_src
#+begin_quote
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.
#+end_quote
*** 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:
#+begin_src c
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);
}
#+end_src
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.
#+begin_quote
A macro =BUFSIZ= é definida em =stdio.h= e expande o valor =8192= (8kB).
#+end_quote
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/.
#+begin_quote
O ponteiro interno do arquivo pode ser obtido e manipulado com as funções
=fseek=, =ftell= e =rewind=.
#+end_quote
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.
#+begin_quote
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).
#+end_quote
Para fechar o arquivo do nosso exemplo:
#+begin_src c
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);
#+end_src