Eu gostaria de fornecer uma perspectiva abstrata e de alto nível.
Concorrência e simultaneidade
As operações de E / S interagem com o ambiente. O ambiente não faz parte do seu programa e não está sob seu controle. O ambiente realmente existe "simultaneamente" com o seu programa. Como acontece com todas as coisas concorrentes, as perguntas sobre o "estado atual" não fazem sentido: não há conceito de "simultaneidade" entre os eventos concorrentes. Muitas propriedades do estado simplesmente não existem simultaneamente.
Deixe-me fazer isso mais preciso: suponha que você queira perguntar: "você tem mais dados". Você pode solicitar isso a um contêiner simultâneo ou ao seu sistema de E / S. Mas a resposta é geralmente impraticável e, portanto, sem sentido. E se o contêiner disser "sim" - quando você tentar ler, ele poderá não ter mais dados. Da mesma forma, se a resposta for "não", no momento em que você tentar ler, os dados poderão ter chegado. A conclusão é que simplesmente existenenhuma propriedade como "Eu tenho dados", pois você não pode agir de maneira significativa em resposta a qualquer resposta possível. (A situação é um pouco melhor com entrada em buffer, onde você pode obter um "sim, eu tenho dados" que constitui algum tipo de garantia, mas você ainda precisa lidar com o caso oposto. E com a saída da situação certamente é tão ruim quanto eu descrevi: você nunca sabe se esse disco ou esse buffer de rede está cheio.)
Portanto, concluímos que é impossível, e de fato não razoável , perguntar a um sistema de E / S se será capaz de executar uma operação de E / S. A única maneira possível de interagir com ele (como em um contêiner simultâneo) é tentar a operação e verificar se foi bem-sucedida ou falhou. Nesse momento em que você interage com o ambiente, então e somente então você pode saber se a interação era realmente possível e, nesse ponto, você deve se comprometer em executar a interação. (Este é um "ponto de sincronização", se você desejar.)
EOF
Agora chegamos ao EOF. EOF é a resposta que você obtém de uma tentativa de operação de E / S. Isso significa que você estava tentando ler ou gravar algo, mas ao fazer isso, não conseguiu ler ou gravar nenhum dado e, em vez disso, foi encontrado o final da entrada ou saída. Isso é verdade para essencialmente todas as APIs de E / S, seja a biblioteca padrão C, iostreams C ++ ou outras bibliotecas. Enquanto as operações de E / S forem bem- sucedidas, você simplesmente não poderá saber se outras operações futuras serão bem-sucedidas. Você sempre deve primeiro tentar a operação e depois responder ao sucesso ou fracasso.
Exemplos
Em cada um dos exemplos, observe com cuidado que primeiro tentamos a operação de E / S e, em seguida, consumimos o resultado, se for válido. Observe ainda que sempre devemos usar o resultado da operação de E / S, embora o resultado tenha diferentes formas e formatos em cada exemplo.
C stdio, leia a partir de um arquivo:
for (;;) {
size_t n = fread(buf, 1, bufsize, infile);
consume(buf, n);
if (n < bufsize) { break; }
}
O resultado que devemos usar é n
o número de elementos que foram lidos (que podem ser tão pequenos quanto zero).
C stdio, scanf
:
for (int a, b, c; scanf("%d %d %d", &a, &b, &c) == 3; ) {
consume(a, b, c);
}
O resultado que devemos usar é o valor de retorno de scanf
, o número de elementos convertidos.
C ++, extração iostreams formatada:
for (int n; std::cin >> n; ) {
consume(n);
}
O resultado que devemos usar é o std::cin
próprio, que pode ser avaliado em um contexto booleano e informa se o fluxo ainda está no good()
estado.
C ++, iostreams getline:
for (std::string line; std::getline(std::cin, line); ) {
consume(line);
}
O resultado que devemos usar é novamente std::cin
, exatamente como antes.
POSIX, write(2)
para liberar um buffer:
char const * p = buf;
ssize_t n = bufsize;
for (ssize_t k = bufsize; (k = write(fd, p, n)) > 0; p += k, n -= k) {}
if (n != 0) { /* error, failed to write complete buffer */ }
O resultado que usamos aqui é k
o número de bytes gravados. O ponto aqui é que só podemos saber quantos bytes foram gravados após a operação de gravação.
POSIX getline()
char *buffer = NULL;
size_t bufsiz = 0;
ssize_t nbytes;
while ((nbytes = getline(&buffer, &bufsiz, fp)) != -1)
{
/* Use nbytes of data in buffer */
}
free(buffer);
O resultado que devemos usar é nbytes
o número de bytes até e incluindo a nova linha (ou EOF se o arquivo não terminar com uma nova linha).
Observe que a função retorna explicitamente -1
(e não o EOF!) Quando ocorre um erro ou atinge o EOF.
Você pode notar que raramente escrevemos a palavra "EOF" real. Geralmente, detectamos a condição de erro de alguma outra maneira que é mais imediatamente interessante para nós (por exemplo, falha em executar a quantidade de E / S que desejávamos). Em todos os exemplos, há algum recurso da API que pode nos dizer explicitamente que o estado EOF foi encontrado, mas isso não é realmente uma informação muito útil. É muito mais um detalhe do que geralmente nos preocupamos. O que importa é se a E / S teve êxito, mais do que como falhou.
Um exemplo final que realmente consulta o estado EOF: suponha que você tenha uma sequência e queira testar se ela representa um número inteiro na sua totalidade, sem bits extras no final, exceto espaços em branco. Usando C ++ iostreams, fica assim:
std::string input = " 123 "; // example
std::istringstream iss(input);
int value;
if (iss >> value >> std::ws && iss.get() == EOF) {
consume(value);
} else {
// error, "input" is not parsable as an integer
}
Usamos dois resultados aqui. O primeiro é iss
o próprio objeto de fluxo, para verificar se a extração formatada foi value
bem - sucedida. Porém, depois de consumir também o espaço em branco, executamos outra operação de E / S / iss.get()
e esperamos que ela falhe como EOF, o que acontece se toda a cadeia já tiver sido consumida pela extração formatada.
Na biblioteca padrão C, você pode obter algo semelhante com as strto*l
funções, verificando se o ponteiro final atingiu o final da sequência de entrada.
A resposta
while(!feof)
está errado porque testa algo irrelevante e falha ao testar algo que você precisa saber. O resultado é que você está executando um código erroneamente que pressupõe que está acessando dados que foram lidos com êxito, quando na verdade isso nunca aconteceu.
feof()
para controlar um loop