Armazenando o caractere EOF (Fim do Arquivo) em um tipo de caractere

11

Eu li no livro The C Programming Language de Dennis Ritchie, que intdeve ser usado para uma variável conter EOF - para torná-la suficientemente grande para que possa manter o valor EOF - não char. Mas o código a seguir funciona bem:

#include<stdio.h> 

main()  { 
  char c; 
  c=getchar(); 
  while(c!=EOF)  { 
    putchar(c); 
    c=getchar(); 
  } 
} 

Quando não houver mais entrada, getcharretorna EOF. E no programa acima, a variável c, com o tipo char, é capaz de segurá-la com sucesso.

Por que isso funciona? Conforme a explicação no livro acima mencionado, o código não deve funcionar.

user1369975
fonte
5
Este código é provável que falhe se você ler um personagem com o valor 0xff. Armazenar o resultado de getchar()em um intresolve esse problema. Sua pergunta é essencialmente a mesma que a pergunta 12.1 na FAQ do comp.lang.c , que é um excelente recurso. (Além disso, main()deve ser int main(void), e não faria mal para adicionar um return 0;antes do fechamento }.)
Keith Thompson
1
@ delnan: O artigo vinculado não está certo sobre como o Unix trata o controle-D. Não fecha o fluxo de entrada; apenas faz com que qualquer fread () bloqueado no console retorne imediatamente com todos os dados ainda não lidos. Muitos programas interpretam um retorno de zero byte de fread () como indicando EOF, mas o arquivo permanecerá aberto e poderá fornecer mais informações.
Supercat

Respostas:

11

Seu código parece funcionar, porque acidentalmente as conversões implícitas de tipo fazem a coisa certa.

getchar()retorna um intcom um valor que se ajusta ao intervalo de unsigned charou é EOF(que deve ser negativo, geralmente é -1). Observe que EOFele próprio não é um personagem, mas um sinal de que não há mais caracteres disponíveis.

Ao armazenar o resultado de getchar()dentro c, há duas possibilidades. O tipo charpode representar o valor; nesse caso, esse é o valor de c. Ou o tipo char não pode representar o valor. Nesse caso, não está definido o que acontecerá. Os processadores Intel apenas cortam os bits altos que não se encaixam no novo tipo (reduzindo efetivamente o módulo de valor 256 para char), mas você não deve confiar nisso.

O próximo passo é comparar ccom EOF. Como EOFé um int, ctambém será convertido em um int, preservando o valor armazenado em c. Se cpuder armazenar o valor de EOF, a comparação será bem-sucedida, mas se nãoc puder armazenar o valor, a comparação falhará, porque houve uma perda irrecuperável de informações durante a conversão para o tipo .EOFchar

Parece que seu compilador escolheu fazer com que o chartipo seja assinado e o valor EOFpequeno o suficiente para caber char. Se charnão tivesse assinado (ou se você tivesse usado unsigned char), seu teste teria falhado, porque unsigned charnão pode conter o valor de EOF.


Observe também que há um segundo problema com o seu código. Como EOFnão é um personagem em si, mas você o força a um chartipo, é muito provável que exista um personagem que seja mal interpretado como sendo EOFe, para metade dos caracteres possíveis, é indefinido se eles serão processados ​​corretamente.

Bart van Ingen Schenau
fonte
A coação para digitar charvalores fora da faixa CHAR_MIN.. CHAR_MAXwill é necessária para gerar um valor definido pela implementação, gerar um padrão de bits que a implementação define como uma representação de interceptação ou gerar um sinal definido pela implementação. Na maioria dos casos, as implementações teriam que passar por muito trabalho extra para fazer algo diferente da redução do complemento de dois. Se as pessoas no Comité de Normas subscreveu a ideia de que os compiladores devem ser encorajados a implementar comportamentos consistentes com a da maioria dos outros compiladores, na ausência de razões para fazer o contrário ...
supercat
... Eu consideraria essa coerção confiável (para não dizer que o código não deve documentar suas intenções, mas isso (signed char)xdeve ser considerado mais claro e tão seguro quanto ((unsigned char)x ^ CHAR_MAX+1))-(CHAR_MAX+1).) Como é, não vejo nenhuma probabilidade de compiladores implementando qualquer outro comportamento em conformidade com o padrão atual; o único perigo seria que o Padrão pudesse ser alterado para interromper o comportamento no suposto interesse de "otimização".
Supercat
@ supercat: O padrão é escrito de tal forma que nenhum compilador precisa produzir código que tenha um comportamento que não é naturalmente suportado pelo processador que ele visa. A maior parte do comportamento indefinido existe porque (no momento da redação do padrão) nem todos os processadores se comportam de maneira consistente. Com os compiladores cada vez mais maduros, os escritores do compilador começaram a tirar proveito do comportamento indefinido para fazer otimizações mais agressivas.
Bart van Ingen Schenau 7/08/15
Historicamente, a intenção do Padrão era principalmente como você descreve, embora o Padrão descreva alguns comportamentos em detalhes suficientes para exigir que os compiladores de algumas plataformas comuns gerem mais código do que seria necessário em uma especificação mais flexível. A coerção de tipo int i=129; signed char c=i;é um desses comportamentos. Relativamente poucos processadores têm uma instrução que seria cigual iquando está no intervalo -127 a +127 e produziria qualquer mapeamento consistente de outros valores de ipara valores no intervalo -128 a +127 que diferiam da redução do complemento de dois ou. ..
supercat
... aumentaria consistentemente um sinal nesses casos. Como o Padrão exige que as implementações produzam um mapeamento consistente ou aumentem consistentemente um sinal, as únicas plataformas em que o Padrão deixaria espaço para algo além da redução do complemento de dois seriam coisas como DSPs com hardware aritmético de saturação. Quanto à base histórica do comportamento indefinido, eu diria que o problema não é apenas com plataformas de hardware. Mesmo em uma plataforma em que o estouro se comportaria de uma maneira muito consistente, pode ser útil ter um compilador interceptá-lo ... #
7898