conteúdo da aula 12

This commit is contained in:
Blau Araujo 2025-04-30 07:50:37 -03:00
parent c4eb988b85
commit 3b99cba06d

View file

@ -87,47 +87,31 @@ descritor de arquivos =0= (entrada padrão).
De pronto, fica evidente a facilidade que nós termos para alterar o tamanho do
vetor =buf= quando necessário: basta alterar a constante simbólica =BUFMAX=.
*** Retorno nulo ambíguo
*** Uma nota sobre o tipo ponteiro para FILE
No caso de sucesso, a função =fgets= retorna o endereço do buffer ou =NULL=,
no caso de um erro, ou se o fim do arquivo for alcançado sem que algo seja lido
(por exemplo, ao ler um arquivo vazio ou numa leitura interativa cancelada com a
combinação de teclas =Ctrl+D=).
No nível do sistema, o acesso a arquivos é sempre estabelecido através de uma
estrutura complexa identificada por um número inteiro: o descritor de arquivos,
assunto da [[../10-dataio/README.org#headline-2][aula 10]]. Então, quando utilizamos chamadas de sistema, como =read= e
=write=, o fluxo de dados (/stream/) é passado como o número de um descritor de
arquivos. Por isso, nós utilizamos as macros:
Como você pode ver, o significado do retorno =NULL= é ambíguo, mas isso só será
uma preocupação se estivermos lendo arquivos. De todo modo, nós podemos testar
o que aconteceu com as funções =feof= e/ou =ferror=.
| Macro | Significado |
|---------------+----------------------------------------------------|
| =STDIN_FILENO= | Valor inteiro relativo ao descritor de arquivos =0=. |
| =STDOUT_FILENO= | Valor inteiro relativo ao descritor de arquivos =1=. |
| =STDERR_FILENO= | Valor inteiro relativo ao descritor de arquivos =2=. |
- =feof(FILE *stream)= - Testa o estado do indicador de fim de arquivo (~0 = não difinido~).
- =ferror(FILE *stream)= - Testa o estado do indicador de erros (~0 = não definido~).
Nas funções da biblioteca padrão, que abstrai outras estruturas de controle
associadas aos descritores de arquivos, os fluxos de dados são passados como
ponteiros para essas estruturas através do tipo =FILE *= (ponteiro para =FILE=).
Os fluxos de dados padrão também são abstraídos como macros definidas na
biblioteca de funções:
Se isso for relevante, nós podemos chamar a função =fgets= desta forma:
#+begin_src c
#include <stdio.h>
#define BUFMAX 10
int main(void) {
char buf[BUFMAX];
if (fgets(buf, BUFMAX, stdin) == NULL) {
// Só preciamos testar se o retorno for NULL...
if (feof(stdin)) {
// Se feof() retornar algo diferente de zero...
fprintf(stderr, "Fim de arquivo alcançado.\n");
} else if (ferror(stdin)) {
// Se ferror() retornar algo diferente de zero...
fprintf(stderr, "Erro de leitura.\n");
}
return 1;
}
...
return 0;
}
#+end_src
| Macro | Significado |
|--------+----------------------------------------------------------|
| =stdin= | Ponteiro para =FILE= associado ao descritor de arquivos =0=. |
| =stdout= | Ponteiro para =FILE= associado ao descritor de arquivos =1=. |
| =stderr= | Ponteiro para =FILE= associado ao descritor de arquivos =2=. |
** O problema da quebra de linha
@ -266,28 +250,198 @@ buf[10] | 0x31 | 0x32 | 0x33 | 0x34 | 0x35 | 0x00 | 0x00 | lixo | lixo | lixo |
+------+------+------+------+------+------+------+------+------+------+
#+end_example
** Uma nota sobre o tipo ponteiro para FILE
** Retorno nulo ambíguo
No nível do sistema, o acesso a arquivos é sempre estabelecido através de uma
estrutura complexa identificada por um número inteiro: o descritor de arquivos,
assunto da [[../10-dataio/README.org#headline-2][aula 10]]. Então, quando utilizamos chamadas de sistema, como =read= e
=write=, o fluxo de dados (/stream/) é passado como o número de um descritor de
arquivos. Por isso, nós utilizamos as macros:
No caso de sucesso, a função =fgets= retorna o endereço do buffer ou =NULL=,
no caso de um erro, ou se o fim do arquivo for alcançado sem que algo seja lido
(por exemplo, ao ler um arquivo vazio ou numa leitura interativa cancelada com a
combinação de teclas =Ctrl+D=).
| Macro | Significado |
|---------------+----------------------------------------------------|
| =STDIN_FILENO= | Valor inteiro relativo ao descritor de arquivos =0=. |
| =STDOUT_FILENO= | Valor inteiro relativo ao descritor de arquivos =1=. |
| =STDERR_FILENO= | Valor inteiro relativo ao descritor de arquivos =2=. |
Como você pode ver, o significado do retorno =NULL= é ambíguo, mas isso só será
uma preocupação se estivermos lendo arquivos. De todo modo, nós podemos testar
o que aconteceu com as funções =feof= e/ou =ferror=.
Nas funções da biblioteca padrão, que abstraem outras estruturas de controle
associadas aos descritores de arquivos, os fluxos de dados são passados como
ponteiros para essas estruturas através do tipo =FILE *= (ponteiro para =FILE=).
Os fluxos de dados padrão também são abstraídos como macros definidas na
biblioteca de funções:
- =feof(FILE *stream)= - Testa o estado do indicador de fim de arquivo (~0 = não difinido~).
- =ferror(FILE *stream)= - Testa o estado do indicador de erros (~0 = não definido~).
| Macro | Significado |
|--------+----------------------------------------------------------|
| =stdin= | Ponteiro para =FILE= associado ao descritor de arquivos =0=. |
| =stdout= | Ponteiro para =FILE= associado ao descritor de arquivos =1=. |
| =stderr= | Ponteiro para =FILE= associado ao descritor de arquivos =2=. |
Se isso for relevante, nós podemos chamar a função =fgets= desta forma:
#+begin_src c
#include <stdio.h>
#define BUFMAX 10
int main(void) {
char buf[BUFMAX];
if (fgets(buf, BUFMAX, stdin) == NULL) {
// Só preciamos testar se o retorno for NULL...
if (feof(stdin)) {
// Se feof() retornar algo diferente de zero...
fprintf(stderr, "Fim de arquivo alcançado.\n");
} else if (ferror(stdin)) {
// Se ferror() retornar algo diferente de zero...
fprintf(stderr, "Erro de leitura.\n");
}
return 1;
}
...
return 0;
}
#+end_src
** Conversão para números
Diferente da função =scanf=, =fgets= apenas lê caracteres e escreve num buffer
como uma string (caracteres terminados com ='\0'=). Isso significa que, se
estivermos interessados em valores numéricos, os bytes no buffer terão que
ser tratado, geralmente em três etapas:
- Remoção da quebra de linha, se houver uma;
- Validação dos caracteres segundo o tipo esperado (=int=, =float=, =double=, etc);
- Conversão.
Nós já falamos sobre a remoção da quebra de linha, então podemos nos concentrar
nas duas etapas seguintes. Porém, os métodos de conversão mais comuns já integram
alguma forma de validação (geralmente, como retornos). Sendo assim, vamos nos
concentrar no que pode ser feito para obter os tipos esperados.
*** Conversão para inteiros com 'sscanf'
Provavelmente, a forma mais simples e direta para converter o conteúdo
de um buffer para tipos numéricos é com a função =sscanf=:
#+begin_src c
char buf[BUFMAX];
int i;
fgets(buf, BUFMAX, stdin);
sscanf(buf, "%d", &i);
#+end_src
Aqui, se houver dígitos válidos no começo de =buf=, eles serão convertidos
em seu correspondente inteiro na base 10, exatamente como seria feito
lendo uma entrada digitada no terminal com a função =scanf= -- e com suas
mesmas limitações:
- A dificuldade de detectar entradas inválidas (por exemplo, =42abc= seria
casado e convertido para o inteiro =42=);
- O retorno será apenas a quantidade de casamentos com a string de formato
que forem convertidos e escritos nas variáveis, ou =EOF=, no caso do fim
do buffer ser alcançado antes de um primeiro casamento ou no caso de
um erro;
- Não oferece uma detecção de erros detalhada, como com as funções que
alteram a variável =errno=.
Além disso, =sscanf= não lida bem com valores fora dos limites do tipo de
destino, por exemplo:
#+begin_src c
#include <stdio.h>
#include <limits.h>
int main(void) {
char str[] = "9999999999";
int i = 0;
sscanf(str, "%d", &i);
printf("Valor de INT_MAX : %d\n", INT_MAX);
printf("Dígitos na string: %s\n", str);
printf("Valor convertido : %d\n", i);
return 0;
}
#+end_src
Neste exemplo, o comportamento de =sscanf= é indeterminado, mas compilaria
sem erros:
#+begin_example
:~$ gcc -Wall teste.c
:~$ ./a.out
Valor de INT_MAX : 2147483647
Dígitos na string: 9999999999
Valor convertido : 1410065407
#+end_example
Aqui, =9999999999= é convertido para um valor com 5 bytes (=0x02540be3ff=),
dos quais, apenas os 4 últimos cabem no espaço do tipo =int= (=0x540be3ff=),
resultando no valor =1410065407=.
*** Conversão para inteiros longos com 'strtol'
A função =strtol= converte a parte inicial de uma string para =long= de
acordo com a base de numeração indicada. O endereço do primeiro caractere
inválido é atribuído a um ponteiro -- e isso nos dá uma excelente forma
de controle da conversão.
Simplificadamente, este seria o protótipo da função:
#+begin_src c
long strtol(char *str, char **endptr, int base);
#+end_src
Exemplo:
#+begin_src c
#include <stdio.h>
#include <stdlib.h>
int main(void) {
char str[] = "1234567abc";
char *end;
long i = strtol(str, &end, 10);
printf("Valor convertido : %ld\n", i);
printf("Primeiro caractere inválido: 0x%02x\n", *end);
return 0;
}
#+end_src
Compilando e executando:
#+begin_example
:~$ gcc -Wall teste.c
:~$ ./a.out
Valor convertido: 1234567
Primeiro caractere inválido: 0x61
#+end_example
#+begin_quote
O caractere =0x61= é =a=, na tabela ASCII.
#+end_quote
Outros detalhes:
- No caso de valores maiores que o limite máximo de um inteiro longo,
o retorno será =LONG_MAX=.
- No caso de valores menores que o limite mínimo de um inteiro longo,
o retorno será =LONG_MIN=.
- Em ambos os casos, a variável =errno= recebe o valor de =ERANGE= (valor
fora da faixa do tipo).
- Se o valor for inválido para a base de numeração, =errno= recebe
o valor de =EINVAL= (valor inválido para a base).
- A descrição do erro em =errno= pode ser impressa com a função =perror=,
chamada logo após =strtol=.
- Se não houver dígitos e caracteres válidos no começo da string,
o endereço do primeiro caractere inválido será o mesmo da string
e a função retornará =0=.
- O ponto negativo de =strtol= é que o retorno é =long= e, se precisarmos
de um valor =int=, teremos que verificar os limites máximo e mínimo
do valor convertido.
*** Outras funções de conversão
- =strtoll= - Idêntica a =strtol=, mas converte para =long long int=.
- =strtod=, =strtof= e =strtold= - Funcionam como =strtol=, mas convertem
para =double=, =float= e =long double=, respectivamente, sem a passagem
de uma base de numeração.
- =atoi=, =atof= e =atol= - São muito simples, mas devem ser evitadas pela
falta de validação e de meios para tratamento de erros.