Ao pesquisar sobre E/S de arquivos em C, me deparei com duas funções: fgetc()
e read()
.
//code1.c
#include <stdio.h>
int main(void)
{
char ch;
ch = fgetc(stdin);
return 0;
}
//code2.c
#include <unistd.h>
int main(void)
{
char ch;
read(STDIN_FILENO, &ch, 1);
return 0;
}
Em ambos os programas acima, se eu digitar hello
:
O primeiro, armazenará a entrada do teclado (stdin)
ch
e o programa simplesmente terminará. O que significa quech
conteráh
e os caracteres restantes simplesmente desaparecerão.No segundo programa, a entrada do teclado também será armazenada em
ch
. Mas os caracteres restantes (ello
) não desaparecerão. Em vez disso, eles serão passados para o terminal como um comando após o término do programa.
Não consigo entender por que isso está acontecendo? É algo relacionado a como as entradas são armazenadas em buffer em C (e por computadores em geral)?
Sim, está relacionado à forma como as entradas são armazenadas em buffer.
As funções do pacote de E/S padrão para leitura de dados (
fgetc()
et al) esperarão até que os dados sejam disponibilizados pelo driver do terminal e então lerão os dados disponíveis do terminal (geralmente uma linha completa — com seu exemplo, os caracteres'h'
,'e'
,'l'
,'l'
,'o'
,'\n'
) e afgetc()
função retornará o primeiro, o'h'
. Consequentemente, os outros caracteres não estarão disponíveis para outros programas.A chamada do sistema
read()
também aguardará que o driver do terminal disponibilize os dados, mas então lerá apenas o primeiro caractere, deixando os outros caracteres disponíveis para outros programas.Em sistemas baseados em POSIX, a
fgetc()
função normalmente usa aread()
chamada de sistema (indiretamente) para obter os dados do terminal, mas normalmente solicita até um buffer cheio de dados, que pode ter de 512 a 8192 caracteres solicitados (ou pode ser maior; geralmente será uma potência de dois), mas aread()
chamada retornará com o que estiver disponível. Isso geralmente é muito menos do que um buffer cheio quando a entrada é um terminal. As regras são um pouco diferentes quando a entrada é um arquivo de disco, pipe ou socket.Observe que a
read()
chamada do sistema não adiciona um byte nulo ao final dos dados, portanto, o que ela lê não são strings.Eu ignorei vários detalhes e ressalvas, buscando manter minha resposta fácil de entender, evitando distorções grosseiras da realidade. Há maneiras de controlar o comportamento dos terminais; descrevi mais ou menos o que acontece no caso padrão.
fgetc(stdin)
usa entrada em buffer, lendo uma linha completa em um buffer e retornando um caractere por vez, enquantoread(STDIN_FILENO, &ch, 1)
não usa entrada em buffer, lendo apenas um caractere e deixando o restante para processamento de entrada futuro.Vou começar dizendo qual é a diferença entre
read
efread
.Sobre
fread
:fread
é uma função da biblioteca stdio.fread
trabalha com "streams" (o que ele chama deFILE *
).fread
amortecedores.fread
pode ler mais do que o solicitado, armazenando o excesso em um buffer.fread
pode executar várias chamadas de sistema.fread
pode bloquear se menos dados do que o solicitado estiverem disponíveis. Ele retornará a quantidade de dados solicitados, a menos que EOF seja encontrado ou ocorra um erro. (Não sei se esse é um comportamento garantido.)Sobre
read
:read
é uma chamada de sistema unix.read
funciona com "descritores de arquivo" (identificadores de arquivo do sistema operacional).read
não armazena em buffer.read
não lerá mais do que o solicitado.read
executa apenas uma chamada de sistema.read
retorna imediatamente se os dados estiverem disponíveis para serem retornados, mesmo se a quantidade de dados for menor que a solicitada. (Não sei se esse é um comportamento garantido.)Onde
fgetc
se encaixa nissofgetc
é uma função da biblioteca stdio assim comofread
. Como tal, os seguintes são equivalentes:Os seguintes são equivalentes:
Devido ao buffer realizado pelas funções stdio, não é aconselhável usar funções stdio e não stdio com o mesmo descritor de arquivo.
Sobre a diferença de comportamento dos seus programas
Como
fgetc
efread
são funções de buffer, elas podem ler mais do que o solicitado. É por isso que seu programa está absorvendoello\n
. O excesso é armazenado no buffer do fluxo para futuras chamadas parafgetc
e/oufread
para retornar. Nenhuma ocorre antes do programa sair, então oello\n
é perdido.Como
read
não armazena em buffer, ele não lê mais do que o solicitado e não consome oello\n
.