mirror of
https://gitlab.com/blau_araujo/cblc.git
synced 2025-05-11 02:56:35 -03:00
conteúdo da aula 13
This commit is contained in:
parent
9f778123b8
commit
3ee62b50a2
1 changed files with 311 additions and 212 deletions
|
@ -11,28 +11,25 @@
|
||||||
A função =read= é um /wrapper/ (uma /"embalagem"/) para simplificar o uso da chamada
|
A função =read= é um /wrapper/ (uma /"embalagem"/) para simplificar o uso da chamada
|
||||||
de sistema =read=. Chamadas de sistema são funções internas que o kernel
|
de sistema =read=. Chamadas de sistema são funções internas que o kernel
|
||||||
implementa para que os programas (executados no /espaço de usuário/) tenham acesso
|
implementa para que os programas (executados no /espaço de usuário/) tenham acesso
|
||||||
a serviços e funcionalidades do sistema (disponíveis no /espaço do kernel/).
|
a serviços e funcionalidades do sistema disponíveis no /espaço do kernel/.
|
||||||
|
|
||||||
Entre essas funcionalidades, estão:
|
Entre essas funcionalidades, estão o acesso a arquivos para leitura e escrita, a
|
||||||
|
alocação de espaço em memória, a criação de processos, o término de programas e
|
||||||
|
muitas outras. Portanto, nossos programas em C não interagem diretamente com o
|
||||||
|
hardware ou com recursos protegidos do sistema. Em vez disso, eles fazem
|
||||||
|
/chamadas de sistema/ para "pedir" que o kernel realize essas operações por eles.
|
||||||
|
|
||||||
- Acesso a arquivos para leitura e escrita;
|
Isso também significa que todas as funções da biblioteca C padrão que nós
|
||||||
- Alocação de espaço em memória;
|
utilizamos até agora, em algum momento, também fazem chamadas de sistema. Por
|
||||||
- Criação de processos;
|
exemplo, a função =fgets= utiliza a chamada de sistema =read= para preencher o
|
||||||
- Término de programas.
|
buffer de entrada do programa e, em seguida, copiar parte desse conteúdo para um
|
||||||
|
determinado endereço na memória: o que também envolve algumas outras chamadas de
|
||||||
|
sistema.
|
||||||
|
|
||||||
Nossos programas em C não interagem diretamente com o hardware ou com recursos
|
Se quisermos utilizar a /chamada de sistema/ =read= para escrever bytes diretamente
|
||||||
protegidos do sistema. Em vez disso, eles fazem /chamadas de sistema/ para
|
no endereço de destino, evitando o buffer de entrada do processo, nós podemos
|
||||||
"pedir" que o kernel realize essas operações por ele. Sendo assim, fica fácil
|
utilizar a /função/ =read= da =glibc=, que é uma interface para a chamada de sistema
|
||||||
concluir que todas as funções da biblioteca C padrão que nós utilizamos até
|
=read= propriamente dita.
|
||||||
agora, em algum momento, fazem chamadas de sistema.
|
|
||||||
|
|
||||||
Por exemplo, a função =fgets= utiliza a chamada de sistema =read= para preencher o
|
|
||||||
buffer de entrada do processo e, em seguida, copiar parte desse conteúdo para o
|
|
||||||
buffer definido no programa para receber uma string. Se quisermos utilizar
|
|
||||||
a chamada de sistema =read= para escrever bytes diretamente no buffer de destino,
|
|
||||||
evitando o buffer de entrada do processo, nós podemos utilizar a função =read=
|
|
||||||
da =glibc=, que é uma interface (um /wrapper/, ou /"embalagem"/) para a chamada de
|
|
||||||
sistema propriamente dita.
|
|
||||||
|
|
||||||
#+begin_quote
|
#+begin_quote
|
||||||
As /man pages/ das interfaces para as chamadas de sistema implementadas na =glibc=
|
As /man pages/ das interfaces para as chamadas de sistema implementadas na =glibc=
|
||||||
|
@ -78,14 +75,9 @@ int main(void) {
|
||||||
Isso fará com que até 10 bytes (=BUFMAX=) recebidos pela entrada padrão
|
Isso fará com que até 10 bytes (=BUFMAX=) recebidos pela entrada padrão
|
||||||
(descritor de arquivos =0=, valor expandido pela macro =STDIN_FILENO=) sejam
|
(descritor de arquivos =0=, valor expandido pela macro =STDIN_FILENO=) sejam
|
||||||
escritos na memória a partir do endereço de =buf=. Mas, diferente de =fgets= e
|
escritos na memória a partir do endereço de =buf=. Mas, diferente de =fgets= e
|
||||||
=scanf=, não haverá a inclusão do terminador nulo (='\0'=).
|
=scanf=, não haverá a inclusão do terminador nulo (='\0'=). Se quisermos
|
||||||
|
garantir um byte para receber o terminador, nós precisamos fazer como na
|
||||||
*** Incluindo o terminador nulo
|
função =fgets=: ler =count - 1= bytes...
|
||||||
|
|
||||||
Se quisermos que o conteúdo do buffer seja uma string, nós teremos que
|
|
||||||
implementar uma forma de acrescentar o terminador nulo -- por exemplo,
|
|
||||||
utilizando a quantidade de bytes lidos, que é retornada pela função =read=
|
|
||||||
em caso de sucesso:
|
|
||||||
|
|
||||||
#+begin_src c
|
#+begin_src c
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
@ -95,11 +87,37 @@ em caso de sucesso:
|
||||||
int main(void) {
|
int main(void) {
|
||||||
|
|
||||||
char buf[BUFMAX];
|
char buf[BUFMAX];
|
||||||
ssize_t count = 0;
|
read(STDIN_FILENO, buf, BUFMAX - 1);
|
||||||
|
|
||||||
if((count = read(STDIN_FILENO, buf, BUFMAX - 1)) > 0) {
|
...
|
||||||
buf[count] = '\0';
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
|
||||||
|
** Conversão para string incluindo o terminador nulo
|
||||||
|
|
||||||
|
Para converter o conteúdo do buffer em uma string, nós temos que
|
||||||
|
implementar uma forma de acrescentar o terminador nulo -- por exemplo,
|
||||||
|
utilizando a quantidade de bytes efetivamente lidos, que é retornada
|
||||||
|
pela função =read= em caso de sucesso:
|
||||||
|
|
||||||
|
#+begin_src c
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define BUFMAX 10
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
|
||||||
|
char buf[BUFMAX];
|
||||||
|
ssize_t bytes = 0;
|
||||||
|
|
||||||
|
if((bytes = read(STDIN_FILENO, buf, BUFMAX - 1)) <= 0) {
|
||||||
|
return 1; // Erro ou nada foi lido
|
||||||
}
|
}
|
||||||
|
// Tratamento do terminador nulo...
|
||||||
|
buf[bytes] = '\0';
|
||||||
|
|
||||||
/* ... */
|
/* ... */
|
||||||
|
|
||||||
|
@ -108,19 +126,19 @@ int main(void) {
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
#+begin_quote
|
#+begin_quote
|
||||||
O retorno =0= indica o fim do arquivo e =-1= indica um erro, cujo identificador é
|
O retorno =0= indica o fim do arquivo e =-1= indica um erro, cujo identificador
|
||||||
atribuído à variável =errno= e pode ser exibido com a função =perror=.
|
é atribuído à variável =errno= e pode ser exibido com a função =perror=.
|
||||||
#+end_quote
|
#+end_quote
|
||||||
|
|
||||||
A solução do exemplo faz com que =read= se comporte quase como a função
|
A solução do exemplo faz com que =read= tenha um comportamento semelhante ao
|
||||||
=fgets=, exceto por não criar um buffer de entrada no /heap/ do processo:
|
de =fgets=:
|
||||||
|
|
||||||
- A leitura é feita até =BUFMAX - 1=;
|
- A leitura é feita até =BUFMAX - 1=;
|
||||||
- O caractere seguinte no buffer (=buf[count]=) recebe ='\0'=;
|
- O caractere seguinte no buffer (=buf[bytes]=) recebe ='\0'=;
|
||||||
- O caractere ='\n'= será preservado se estiver no limite de leitura.
|
- O caractere ='\n'= será preservado se estiver no limite de leitura.
|
||||||
|
|
||||||
Para demonstrar, digamos que o usuário digitou =12345= e teclou =Enter=. O valor
|
Para demonstrar, digamos que o usuário digitou =12345= e teclou =Enter=. O valor
|
||||||
de =count= será =6= e os bytes em =buf= serão:
|
de =bytes= será =6= e os bytes em =buf= serão:
|
||||||
|
|
||||||
#+begin_example
|
#+begin_example
|
||||||
buf[0] buf[1] buf[2] buf[3] buf[4] buf[5] buf[6] buf[7] buf[8] buf[9]
|
buf[0] buf[1] buf[2] buf[3] buf[4] buf[5] buf[6] buf[7] buf[8] buf[9]
|
||||||
|
@ -129,7 +147,7 @@ buf[10] | 0x31 | 0x32 | 0x33 | 0x34 | 0x35 | 0x0A | lixo | lixo | lixo | lixo |
|
||||||
+------+------+------+------+------+------+------+------+------+------+
|
+------+------+------+------+------+------+------+------+------+------+
|
||||||
#+end_example
|
#+end_example
|
||||||
|
|
||||||
Depois de alterado o byte em =buf[count]= (=buf[6]=), nós teremos:
|
Depois de alterado o byte em =buf[bytes]= (=buf[6]=), nós teremos:
|
||||||
|
|
||||||
#+begin_example
|
#+begin_example
|
||||||
buf[0] buf[1] buf[2] buf[3] buf[4] buf[5] buf[6] buf[7] buf[8] buf[9]
|
buf[0] buf[1] buf[2] buf[3] buf[4] buf[5] buf[6] buf[7] buf[8] buf[9]
|
||||||
|
@ -147,7 +165,7 @@ buf[10] | 0x31 | 0x32 | 0x33 | 0x34 | 0x35 | 0x36 | 0x37 | 0x38 | 0x39 | lixo |
|
||||||
+------+------+------+------+------+------+------+------+------+------+
|
+------+------+------+------+------+------+------+------+------+------+
|
||||||
#+end_example
|
#+end_example
|
||||||
|
|
||||||
Os caracteres =0x30= (=0=) e =0x0a= (='\n'=) não serão lidos, o valor de =count= será =9=
|
Os caracteres =0x30= (=0=) e =0x0a= (='\n'=) não serão lidos, o valor de =bytes= será =9=
|
||||||
e =buf[9]= receberá o terminador nulo:
|
e =buf[9]= receberá o terminador nulo:
|
||||||
|
|
||||||
#+begin_example
|
#+begin_example
|
||||||
|
@ -169,20 +187,22 @@ estivermos lendo...
|
||||||
- Dados para cálculos de /checksum/ e /hash/;
|
- Dados para cálculos de /checksum/ e /hash/;
|
||||||
- Dados para compressão ou criptografia...
|
- Dados para compressão ou criptografia...
|
||||||
|
|
||||||
Entre outras tantas situações.
|
Entre outras tantas situações.
|
||||||
|
|
||||||
** O buffer do terminal
|
** O buffer do terminal
|
||||||
|
|
||||||
Quando um programa com a função =read= é executado interativamente, os dados
|
Quando um programa com a função =read= é executado para receber dados digitados
|
||||||
digitados são inicialmente armazenados em um buffer gerenciado pelo terminal.
|
em um terminal, a digitação é acumulada em um buffer gerenciado pelo terminal
|
||||||
Ao chamar a função =read=, o programa consome parte desses dados da entrada padrão
|
e só será enviada para o processo do programa quando o usuário teclar =Enter=.
|
||||||
e os bytes que não forem consumidos permanecerão no buffer do terminal.
|
No programa, a função =read= consome parte desses dados e os bytes que não
|
||||||
|
forem consumidos permanecerão no buffer do terminal.
|
||||||
|
|
||||||
Esses dados residuais não são descartados e permanecem disponíveis para futuras
|
Os dados residuais não são descartados e permanecem disponíveis para futuras
|
||||||
chamadas de leitura -- inclusive por processos subsequentes, caso o processo
|
chamadas de leitura (inclusive por processos subsequentes, caso o processo
|
||||||
atual termine. Isso pode causar efeitos inesperados em programas interativos
|
do programa termine) ou até o terminal ser fechado. Isso pode causar efeitos
|
||||||
executados na sequência (como o próprio shell, por exemplo), pois os caracteres
|
inesperados em programas interativos executados na sequência (como o próprio
|
||||||
digitados anteriormente, mas ainda não consumidos, serão lidos automaticamente.
|
shell, por exemplo), pois o conteúdo remanescente no buffer do terminal será
|
||||||
|
lido automaticamente.
|
||||||
|
|
||||||
Veja este exemplo:
|
Veja este exemplo:
|
||||||
|
|
||||||
|
@ -195,13 +215,13 @@ Veja este exemplo:
|
||||||
int main(void) {
|
int main(void) {
|
||||||
|
|
||||||
char buf[BUFMAX];
|
char buf[BUFMAX];
|
||||||
ssize_t count = 0;
|
ssize_t bytes = 0;
|
||||||
|
|
||||||
if((count = read(STDIN_FILENO, buf, BUFMAX - 1)) <= 0) {
|
if((bytes = read(STDIN_FILENO, buf, BUFMAX - 1)) <= 0) {
|
||||||
return 1;
|
return 1; // Erro ou nada foi lido
|
||||||
}
|
}
|
||||||
// Tratamento do terminador nulo...
|
// Tratamento do terminador nulo...
|
||||||
buf[count] = '\0';
|
buf[bytes] = '\0';
|
||||||
|
|
||||||
printf("%s\n", buf);
|
printf("%s\n", buf);
|
||||||
|
|
||||||
|
@ -216,45 +236,38 @@ Compilando e executando:
|
||||||
:~$ ./a.out
|
:~$ ./a.out
|
||||||
123456789012345
|
123456789012345
|
||||||
123456789
|
123456789
|
||||||
:~$ 012345
|
:~$ 012345 <-- O Bash tentou interpretar e executar o conteúdo do buffer!
|
||||||
bash: 012345: comando não encontrado
|
bash: 012345: comando não encontrado
|
||||||
#+end_example
|
#+end_example
|
||||||
|
|
||||||
A função =read= consumiu apenas os 9 primeiros caracteres digitados, os 6
|
A função =read= consumiu apenas os 9 primeiros caracteres digitados, mas os 6
|
||||||
caracteres restantes continuaram no buffer do terminal e foram lidos pelo
|
caracteres restantes continuaram no buffer do terminal e foram lidos e
|
||||||
processo do shell (interativo) que tentou executá-los como um comando.
|
interpretados pelo processo do shell (interativo) como a linha de um comando.
|
||||||
|
|
||||||
A solução para isso é simples (e já conhecida por nós): consumir todos os
|
*** Descarga do buffer do terminal
|
||||||
bytes restantes na entrada padrão:
|
|
||||||
|
A solução para isso é simples e já é velha conhecida: consumir todos os
|
||||||
|
bytes restantes na entrada padrão, por exemplo, com a função que nós
|
||||||
|
criamos em outras aulas...
|
||||||
|
|
||||||
#+begin_src c
|
#+begin_src c
|
||||||
#include <stdio.h>
|
void flush_stdin(void) {
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#define BUFMAX 10
|
|
||||||
|
|
||||||
int main(void) {
|
|
||||||
|
|
||||||
char buf[BUFMAX];
|
|
||||||
ssize_t count = 0;
|
|
||||||
|
|
||||||
if((count = read(STDIN_FILENO, buf, BUFMAX - 1)) <= 0) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
// Tratamento do terminador nulo...
|
|
||||||
buf[count] = '\0';
|
|
||||||
|
|
||||||
// Tratamento dos caracteres restantes no buffer do terminal...
|
|
||||||
char c;
|
char c;
|
||||||
while((c = getchar()) != '\n' && c != EOF);
|
while((c = getchar()) != '\n' && c != EOF);
|
||||||
|
|
||||||
printf("%s\n", buf);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
Compilando e executando novamente:
|
Mas esta função utiliza =getchar= que, assim como =fgets= e =scanf=, fará a
|
||||||
|
leitura do buffer de entrada criado no processo pela =glibc=. Certamente,
|
||||||
|
o conteúdo do buffer do terminal será copiado para o buffer de entrada do
|
||||||
|
programa. Porém, sem garantias de que haverá algo no buffer do terminal,
|
||||||
|
pode acontecer que a nossa função fique presa em =getchar= até que o usuário
|
||||||
|
tecle =Enter= novamente, enviando a quebra de linha que o loop =while= espera
|
||||||
|
para terminar.
|
||||||
|
|
||||||
|
Por exemplo, utilizando a função =flush_stdin=, o nosso exemplo funcionaria
|
||||||
|
corretamente quando houvesse mais caracteres digitados do que o =read=
|
||||||
|
pode consumir:
|
||||||
|
|
||||||
#+begin_example
|
#+begin_example
|
||||||
:~$ gcc -Wall teste.c
|
:~$ gcc -Wall teste.c
|
||||||
|
@ -264,9 +277,147 @@ Compilando e executando novamente:
|
||||||
:~$
|
:~$
|
||||||
#+end_example
|
#+end_example
|
||||||
|
|
||||||
** Definindo um prompt
|
Contudo, digitando menos caracteres do que o limite, nós teríamos que
|
||||||
|
teclar =Enter= duas vezes: a primeira para a leitura de =read= e a segunda
|
||||||
|
para =getchar=...
|
||||||
|
|
||||||
O nosso programa precisa de um prompt, então vamos implementá-lo:
|
#+begin_example
|
||||||
|
:~$ gcc -Wall teste.c
|
||||||
|
:~$ ./a.out
|
||||||
|
12345
|
||||||
|
<--- Aguardando um segundo ENTER
|
||||||
|
12345
|
||||||
|
<--- Quebra de linha na string impressa
|
||||||
|
:~$
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
A segunda linha em branco era esperada porque nós não fizemos nada para
|
||||||
|
remover a quebra de linha lida por =read=, mas a primeira aconteceu porque
|
||||||
|
eu tive que teclar =Enter= mais uma vez para que =getchar= tivesse algo para
|
||||||
|
ler.
|
||||||
|
|
||||||
|
*** Implementação de =flush_stdin= com a chamada =read=
|
||||||
|
|
||||||
|
Antes de passarmos à solução da descarga do buffer do terminal, vamos
|
||||||
|
aproveitar que estamos lidando com chamadas de sistema para implementar
|
||||||
|
uma versão da função =flush_stdin= com =read=, em vez de =getchar=:
|
||||||
|
|
||||||
|
#+begin_src c
|
||||||
|
void flush_tty_buffer(void) {
|
||||||
|
char c;
|
||||||
|
while (read(STDIN_FILENO, &c, 1) == 1 && c != '\n');
|
||||||
|
}
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
#+begin_quote
|
||||||
|
*Importante!* Tanto =read= quanto =STDIN_FILENO= dependem da inclusão de =unistd.h=.
|
||||||
|
#+end_quote
|
||||||
|
|
||||||
|
O princípio é o mesmo, só que, desta vez, o /loop/ vai continuar enquanto
|
||||||
|
=read= retornar que leu 1 byte e este byte for diferente de ='\n'=. A outra
|
||||||
|
diferença é que, em vez de ler o buffer do programa, a nossa nova função
|
||||||
|
lerá diretamente o buffer do terminal.
|
||||||
|
|
||||||
|
#+begin_quote
|
||||||
|
Esta função não poderia ser utilizada se os bytes remanescentes estivessem
|
||||||
|
no buffer de entrada do programa!
|
||||||
|
#+end_quote
|
||||||
|
|
||||||
|
Mesmo assim, o problema continua: se o buffer do terminal estiver vazio,
|
||||||
|
=read= ficará esperando o recebimento de uma linha, o que só acontecerá
|
||||||
|
quando nós digitarmos novamente um =Enter=.
|
||||||
|
|
||||||
|
*** Descarga condicional do buffer do terminal
|
||||||
|
|
||||||
|
Se a causa do problema é a possibilidade do buffer do terminal estar vazio,
|
||||||
|
basta condicionar a chamada de =flush_stdin=, ou de =flush_tty_buffer=, à
|
||||||
|
verificação desta condição, o que pode ser avaliado de, pelo menos, duas
|
||||||
|
formas: utilizando os bytes lidos e a sua quantidade ou verificando o
|
||||||
|
estado do buffer do terminal.
|
||||||
|
|
||||||
|
*Solução 1: Teste da presença de ='\n'=*
|
||||||
|
|
||||||
|
#+begin_example
|
||||||
|
buf[0] buf[1] buf[2] buf[3] buf[4] buf[5] buf[6] buf[7] buf[8] buf[9]
|
||||||
|
+------+------+------+------+------+------+------+------+------+------+
|
||||||
|
buf[10] | 0x31 | 0x32 | 0x33 | 0x34 | 0x35 | 0x36 | 0x37 | 0x38 | 0x0A | 0X00 |
|
||||||
|
+------+------+------+------+------+------+------+------+------+------+
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
Aqui, a digitação foi =12345678\n= (exatamente =BUFMAX - 1= bytes) e, mesmo que
|
||||||
|
fosse digitada qualquer quantidade de bytes menor que essa, o buffer do
|
||||||
|
terminal estaria vazio e o caractere ='\n'= estaria presente na string. Logo,
|
||||||
|
nós poderíamos utilizar a seguinte condição:
|
||||||
|
|
||||||
|
#+begin_src c
|
||||||
|
if (buf[bytes - 1] != '\n') {
|
||||||
|
flush_stdout(); // Ou flush_tty_buffer
|
||||||
|
}
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
É uma solução interessante porque, além de simples, ela já verifica a
|
||||||
|
existência da quebra de linha na string e nós poderíamos aproveitar, se
|
||||||
|
fosse o caso, para removê-la:
|
||||||
|
|
||||||
|
#+begin_src c
|
||||||
|
if (buf[bytes - 1] == '\n') {
|
||||||
|
buf[bytes - 1] = '\0'; // Remoção da quabra de linha
|
||||||
|
} else {
|
||||||
|
flush_stdout(); // Ou flush_tty_buffer
|
||||||
|
}
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
*Solução 2: verificação do estado do buffer do terminal*
|
||||||
|
|
||||||
|
A chamada de sistema =ioctl=, no cabeçalho =sys/ioctl.h=, manipula parâmetros
|
||||||
|
de dispositivos especiais (como terminais, por exemplo). Seus argumentos
|
||||||
|
são:
|
||||||
|
|
||||||
|
- O descritor de arquivos do dispositivo (ex.: =0= ou =STDIN_FILENO=);
|
||||||
|
- O número da operação com o dispositivo (ex.: =FIONREAD=, quantidade de bytes
|
||||||
|
disponíveis para leitura imediata);
|
||||||
|
- Um ponteiro para o endereço que vai receber a informação obtida.
|
||||||
|
|
||||||
|
Sendo assim, nós poderíamos criar uma função para retornar =0=, no caso de
|
||||||
|
buffer vazio, ou a quantidade de bytes remanescentes no buffer do terminal:
|
||||||
|
|
||||||
|
#+begin_src c
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
int is_tty_empty(void) {
|
||||||
|
int r = 0;
|
||||||
|
return (ioctl(STDIN_FILENO, FIONREAD, &r) == 0);
|
||||||
|
}
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
Portanto, nossa função retornaria =1= (verdadeiro), se o buffer do terminal
|
||||||
|
estivesse vazio, ou =0= (falso), se não estivesse.
|
||||||
|
|
||||||
|
#+begin_quote
|
||||||
|
A ideia de criar uma função, em vez de utilizar =ioctl= diretamente, tem a
|
||||||
|
ver com a atribuição de um valor semântico ao código.
|
||||||
|
#+end_quote
|
||||||
|
|
||||||
|
No nosso exemplo, ela poderia ser utilizada desta maneira:
|
||||||
|
|
||||||
|
#+begin_src c
|
||||||
|
if (!is_tty_empty()) {
|
||||||
|
flush_stdout(); // Ou flush_tty_buffer
|
||||||
|
}
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
*Qual solução utilizar?*
|
||||||
|
|
||||||
|
Não existe uma resposta certa neste caso: tudo depende do contexto do
|
||||||
|
programa. Se eu estivesse buscando uma solução mais robusta e aplicável
|
||||||
|
em diversas situações, eu escolheria a segunda (=ioctl=), mas escolheria
|
||||||
|
a primeira em contextos menos rigorosos, onde a quebra de linha tivesse
|
||||||
|
que ser detectada e removida da string de qualquer forma.
|
||||||
|
|
||||||
|
Eu vou optar pela primeira solução desta vez, então o nosso código ficará
|
||||||
|
assim:
|
||||||
|
|
||||||
#+begin_src c
|
#+begin_src c
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
@ -274,23 +425,30 @@ O nosso programa precisa de um prompt, então vamos implementá-lo:
|
||||||
|
|
||||||
#define BUFMAX 10
|
#define BUFMAX 10
|
||||||
|
|
||||||
|
void flush_tty_buffer(void) {
|
||||||
|
char c;
|
||||||
|
while (read(STDIN_FILENO, &c, 1) == 1 && c != '\n');
|
||||||
|
}
|
||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
|
|
||||||
char buf[BUFMAX];
|
char buf[BUFMAX];
|
||||||
ssize_t count = 0;
|
ssize_t bytes = 0;
|
||||||
|
|
||||||
// Prompt...
|
if((bytes = read(STDIN_FILENO, buf, BUFMAX - 1)) <= 0) {
|
||||||
printf("Digite algo: ");
|
return 1; // Erro ou nada foi lido
|
||||||
|
|
||||||
if((count = read(STDIN_FILENO, buf, BUFMAX - 1)) <= 0) {
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tratamento do terminador nulo...
|
// Tratamento do terminador nulo...
|
||||||
buf[count] = '\0';
|
buf[bytes] = '\0';
|
||||||
|
|
||||||
// Tratamento dos caracteres restantes no buffer do terminal...
|
if (buf[bytes - 1] == '\n') {
|
||||||
char c;
|
// Remoção condicional da quebra de linha...
|
||||||
while((c = getchar()) != '\n' && c != EOF);
|
buf[bytes - 1] = '\0';
|
||||||
|
} else {
|
||||||
|
// Esvaziamento condicional do buffer do terminal...
|
||||||
|
flush_tty_buffer(); // Ou flush_stdin
|
||||||
|
}
|
||||||
|
|
||||||
printf("%s\n", buf);
|
printf("%s\n", buf);
|
||||||
|
|
||||||
|
@ -298,6 +456,41 @@ int main(void) {
|
||||||
}
|
}
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
|
Compilando e testando...
|
||||||
|
|
||||||
|
#+begin_example
|
||||||
|
:~$ gcc -Wall teste.c
|
||||||
|
:~$ ./a.out
|
||||||
|
12345 <-- 6 bytes digitados (buffer tty vazio)
|
||||||
|
12345 <-- Quebra de linha removida
|
||||||
|
:~$ ./a.out
|
||||||
|
12345678 <-- 9 bytes digitados (buffer vazio)
|
||||||
|
12345678 <-- Quebra de linha removida
|
||||||
|
:~$ ./a.out
|
||||||
|
123456789012345 <-- 16 bytes digitados (7 bytes no buffer tty)
|
||||||
|
123456789 <-- Não havia quebra de linha para remover
|
||||||
|
:~$
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
** Definindo um prompt
|
||||||
|
|
||||||
|
O nosso programa precisa de um prompt, então vamos implementá-lo:
|
||||||
|
|
||||||
|
#+begin_src c
|
||||||
|
...
|
||||||
|
|
||||||
|
char buf[BUFMAX];
|
||||||
|
ssize_t bytes = 0;
|
||||||
|
|
||||||
|
printf("Digite algo: "); // Nosso prompt!
|
||||||
|
|
||||||
|
if((bytes = read(STDIN_FILENO, buf, BUFMAX - 1)) <= 0) {
|
||||||
|
return 1; // Erro ou nada foi lido
|
||||||
|
}
|
||||||
|
|
||||||
|
...
|
||||||
|
#+end_src
|
||||||
|
|
||||||
Agora, nós temos a impressão da string ="Digite algo: "= antes da leitura
|
Agora, nós temos a impressão da string ="Digite algo: "= antes da leitura
|
||||||
do terminal. Entretanto, ao compilar e executar o programa, nada é exibido
|
do terminal. Entretanto, ao compilar e executar o programa, nada é exibido
|
||||||
até nós teclarmos =Enter=:
|
até nós teclarmos =Enter=:
|
||||||
|
@ -306,55 +499,42 @@ até nós teclarmos =Enter=:
|
||||||
:~$ gcc -Wall teste.c
|
:~$ gcc -Wall teste.c
|
||||||
:~$ ./a.out
|
:~$ ./a.out
|
||||||
1234567890
|
1234567890
|
||||||
Digite algo: 123456789
|
Digite algo: 123456789 <-- O prompt só foi impresso aqui!
|
||||||
:~$
|
:~$
|
||||||
#+end_example
|
#+end_example
|
||||||
|
|
||||||
Isso acontece porque =printf= utiliza o buffer de saída do programa (criado
|
Isso acontece porque =printf= utiliza o buffer de saída do programa (criado
|
||||||
pela biblioteca padrão) e, por padrão, os dados na saída podem ser:
|
pela biblioteca padrão) e, por padrão, os dados na saída podem ser:
|
||||||
|
|
||||||
- Totalmente bufferizados: quando o programa escreve em arquivos ou pipes;
|
- /Totalmente bufferizados/: quando o programa escreve em arquivos ou pipes;
|
||||||
- Bufferizados por linha: quando a escrita é em um terminal.
|
- /Bufferizados por linha/: quando a escrita é em um terminal.
|
||||||
|
|
||||||
Como estamos escrevendo no terminal, a saída de =printf= é bufferizada por
|
Como estamos escrevendo no terminal, a saída de =printf= é /bufferizada por
|
||||||
linha e só é liberada quando o buffer de saída fica cheio (~BUFSIZ = 8192 bytes~),
|
linha/ e só é liberada quando o buffer de saída fica cheio (geralmente,
|
||||||
quando recebe o caractere de quebra de linha (='\n'=) ou quando é esvaziada
|
=8192 bytes=), quando recebe o caractere de quebra de linha (='\n'=) ou quando
|
||||||
explicitamente (por exemplo, com =fflush(stdout)=).
|
é esvaziada explicitamente.
|
||||||
|
|
||||||
No caso do exemplo, a saída não foi descarregada porque a string na chamada
|
No caso do exemplo, a saída não foi descarregada porque a string na chamada
|
||||||
de =printf= não tem uma quebra de linha (exatamente como nós queremos). Uma
|
de =printf= não tem uma quebra de linha (exatamente como nós queremos). Uma
|
||||||
solução simples, porém, é descarregar o buffer explicitamente:
|
solução simples, porém, é descarregar o buffer explicitamente com a função
|
||||||
|
=fflush=:
|
||||||
|
|
||||||
#+begin_src c
|
#+begin_src c
|
||||||
#include <stdio.h>
|
...
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#define BUFMAX 10
|
char buf[BUFMAX];
|
||||||
|
ssize_t count = 0;
|
||||||
|
|
||||||
int main(void) {
|
// Prompt...
|
||||||
|
printf("Digite algo: ");
|
||||||
|
// Descarga do buffer de saída...
|
||||||
|
fflush(stdout);
|
||||||
|
|
||||||
char buf[BUFMAX];
|
if((count = read(STDIN_FILENO, buf, BUFMAX - 1)) <= 0) {
|
||||||
ssize_t count = 0;
|
return 1;
|
||||||
|
|
||||||
// Prompt...
|
|
||||||
printf("Digite algo: ");
|
|
||||||
// Descarga do buffer de saída...
|
|
||||||
fflush(stdout);
|
|
||||||
|
|
||||||
if((count = read(STDIN_FILENO, buf, BUFMAX - 1)) <= 0) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
// Tratamento do terminador nulo...
|
|
||||||
buf[count] = '\0';
|
|
||||||
|
|
||||||
// Tratamento dos caracteres restantes no buffer do terminal...
|
|
||||||
char c;
|
|
||||||
while((c = getchar()) != '\n' && c != EOF);
|
|
||||||
|
|
||||||
printf("%s\n", buf);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
...
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
Compilando e testando:
|
Compilando e testando:
|
||||||
|
@ -367,91 +547,10 @@ Digite algo: 1234567890
|
||||||
:~$
|
:~$
|
||||||
#+end_example
|
#+end_example
|
||||||
|
|
||||||
** Ainda temos um problema!
|
|
||||||
|
|
||||||
Acontece, porém, que os nossos testes estão mascarando outro problema.
|
|
||||||
Observe o que acontece quando digitamos menos caracteres do que a função
|
|
||||||
=read= espera ler:
|
|
||||||
|
|
||||||
#+begin_example
|
|
||||||
:~$ gcc -Wall teste.c
|
|
||||||
:~$ ./a.out
|
|
||||||
Digite algo: 12345
|
|
||||||
|
|
||||||
12345
|
|
||||||
|
|
||||||
:~$
|
|
||||||
#+end_example
|
|
||||||
|
|
||||||
A segunda linha em branco era esperada porque a quebra de linha da minha
|
|
||||||
digitação foi preservada, mas a primeira linha em branco aconteceu
|
|
||||||
porque eu tive que teclar =Enter= duas vezes!
|
|
||||||
|
|
||||||
Isso aconteceu por causa da forma como estamos descarregando o restante do
|
|
||||||
buffer de entrada do terminal:
|
|
||||||
|
|
||||||
#+begin_src c
|
|
||||||
// Tratamento dos caracteres restantes no buffer do terminal...
|
|
||||||
char c;
|
|
||||||
while((c = getchar()) != '\n' && c != EOF);
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
Quando digitamos menos caracteres do que o =read= espera ler, não há sobras
|
|
||||||
no buffer do terminal e não há quebras de linha nem uma indicação de fim de
|
|
||||||
arquivo (a menos que o usuário tecle =Ctrl+D=). Por isso, a limpeza do buffer
|
|
||||||
deve ser condicionada de uma das seguintes formas:
|
|
||||||
|
|
||||||
- Ou verificamos se o penúltimo caractere no buffer (=buf[count - 1]=) é
|
|
||||||
diferente de ='\n'=, o que indicaria que o buffer não foi consumido
|
|
||||||
completamente;
|
|
||||||
- Ou verificamos se o buffer está vazio ou não com a chamada de sistema
|
|
||||||
=ioctl=.
|
|
||||||
|
|
||||||
#+begin_quote
|
#+begin_quote
|
||||||
Experimente as duas soluções e escolha a sua preferida.
|
O problema da /bufferização por linha/ não é exatamente causado pelo uso da
|
||||||
|
chamada de sistema =read=. Na verdade, as funções de mais alto nível (como
|
||||||
|
=fgets= e =scanf=) é que são implementadas com mecanismos para descargar o buffer
|
||||||
|
de saída automaticamente.
|
||||||
#+end_quote
|
#+end_quote
|
||||||
|
|
||||||
*** Solução com o conteúdo de =buf[count - 1]=
|
|
||||||
|
|
||||||
#+begin_src c
|
|
||||||
// Verificar se '\n' faz parte da string...
|
|
||||||
if (buf[count - 1] != '\n') {
|
|
||||||
// Tratamento dos caracteres restantes no buffer do terminal...
|
|
||||||
char c;
|
|
||||||
while((c = getchar()) != '\n' && c != EOF);
|
|
||||||
}
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
*** Solução com =ioctl=
|
|
||||||
|
|
||||||
A chamada de sistema =ioctl=, no cabeçalho =sys/ioctl.h=, manipula parâmetros
|
|
||||||
de dispositivos especiais (como terminais, por exemplo). Seus argumentos
|
|
||||||
são:
|
|
||||||
|
|
||||||
- O descritor de arquivos do dispositivo (ex.: =0= ou =STDIN_FILENO=);
|
|
||||||
- O número da operação com o dispositivo (ex.: =FIONREAD=, quantidade de bytes
|
|
||||||
disponíveis para leitura imediata);
|
|
||||||
- Um ponteiro para o endereço que vai receber a informação obtida.
|
|
||||||
|
|
||||||
Sendo assim, nós poderíamos utilizá-la desta forma para determinar se
|
|
||||||
o buffer do terminal contém alguma coisa (=0= indica buffer vazio):
|
|
||||||
|
|
||||||
#+begin_src c
|
|
||||||
#include <sys/ioctl.h>
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
int main(void) {
|
|
||||||
...
|
|
||||||
|
|
||||||
// Verificar se o buffer está vazio...
|
|
||||||
int r = 0;
|
|
||||||
if (ioctl(STDIN_FILENO, FIONREAD, &r) != 0) {
|
|
||||||
// Tratamento dos caracteres restantes no buffer do terminal...
|
|
||||||
char c;
|
|
||||||
while((c = getchar()) != '\n' && c != EOF);
|
|
||||||
}
|
|
||||||
...
|
|
||||||
}
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue