Збереження символу EOF (Кінець файлу) у тип char


11

Я читав у книзі " Програмування на мову С Денніса Річі", яку intнеобхідно використовувати для змінної для зберігання EOF - щоб зробити її достатньо великою, щоб вона могла містити значення EOF - ні char. Але наступний код працює добре:

#include<stdio.h> 

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

Коли більше немає введення, getcharповертається EOF. І у вищенаведеній програмі змінна cз типом char здатна успішно утримувати її.

Чому це працює? Відповідно до пояснення у вищезгаданій книзі, код не повинен працювати.



5
Цей код, ймовірно, не вдасться, якщо ви прочитаєте символ зі значенням 0xff. Збереження результату getchar()в Ап intвирішує цю проблему. Ваше запитання по суті те саме, що питання 12.1 в FAQ. comp.lang.c , що є прекрасним ресурсом. (Крім того, main()має бути int main(void), і не завадило б додати return 0;до закриття }.)
Кіт Томпсон,

1
@delnan: Пов'язана стаття не зовсім правильна щодо того, як Unix ставиться до контрольно-D. Він не закриває вхідний потік; це просто викликає негайне повернення будь-якого fread (), який блокує консоль, з будь-якими ще непрочитаними даними. Багато програм інтерпретують нульовий байт повернення від fread () як вказівку EOF, але файл насправді залишатиметься відкритим і зможе подати більше вводу.
supercat

Відповіді:


11

Здається, ваш код працює, оскільки випадкові перетворення типу випадково трапляються правильно.

getchar()повертає значення intзі значенням, яке або відповідає діапазону, unsigned charабо є EOF(яке повинно бути негативним, зазвичай це -1). Зауважте, що EOFсам по собі не символ, а сигнал, що більше символів немає.

При зберіганні результату з getchar()in cє дві можливості. Або тип charможе представляти значення, і в цьому випадку це значення c. Або тип char не може представляти значення. У цьому випадку не визначено, що буде. Процесори Intel просто відсікають високі біти, які не відповідають новому типу (ефективно зменшуючи значення модуля 256 для char), але на це не варто покладатися.

Наступний крок - порівняння cз EOF. Як EOFі є int, cбуде перетворено intв а, зберігаючи значення, збережене в c. Якщо cможе зберігати значення EOF, то порівняння буде успішним, але якщо cможе НЕ зберігати значення, то порівняння буде не в змозі , тому що там була безнадійною втрати інформації при перетворенні EOFдо типу char.

Здається, ваш компілятор вирішив зробити charтип підписаним і значенням EOFдостатньо малим, щоб вмістити його char. Якби не charбуло підписано (або якщо ви використовувались unsigned char), ваш тест не вдався б, оскільки unsigned charне може утримувати значення EOF.


Також зауважте, що з вашим кодом є друга проблема. Оскільки EOFце не сам персонаж, але ви примушуєте його до charтипу, там, швидше за все, персонаж неправильно трактується як буття, EOFі для половини можливих символів він не визначений, якщо вони будуть оброблені правильно.


Примушування до введення charзначень за межами діапазону CHAR_MIN.. CHAR_MAXпотрібно буде або дати значення, визначене реалізацією, отримати біт-шаблон, який реалізація визначає як уявлення про пастку, або підняти сигнал, визначений реалізацією. У більшості випадків реалізаціям доведеться пройти багато додаткових робіт, щоб зробити що-небудь, крім скорочення двох доповнень. Якщо люди з Комітету з стандартів підписалися на думку про те, що компіляторів слід заохочувати застосовувати поведінку, що відповідає поведінці інших компіляторів, якщо немає причин робити інше ...
supercat

... Я вважав би такий примус надійним (не кажучи про те, що код не повинен задокументувати свої наміри, але (signed char)xце слід вважати більш чітким і таким же безпечним, як ((unsigned char)x ^ CHAR_MAX+1))-(CHAR_MAX+1)). Як це є, я не бачу жодної ймовірності компілятори, що реалізують будь-яку іншу поведінку, що відповідає сьогоднішньому стандарту; одна з небезпек полягала б у тому, що Стандарт може бути змінений, щоб порушити поведінку в інтересах "оптимізації".
supercat

@supercat: Стандарт написаний таким чином, що жоден компілятор не повинен створювати код, який має поведінку, яка природно не підтримується процесором, на який він орієнтується. Більшість невизначеної поведінки є там, оскільки (на момент написання стандарту) не всі процесори вели себе послідовно. Коли компілятори стають більш зрілими, автори-компілятори почали користуватися невизначеною поведінкою, щоб зробити більш агресивні оптимізації.
Барт ван Інген Шенау

Історично наміри Стандарту були здебільшого такими, як ви описуєте, хоча Стандарт описує деякі поведінки досить детально, щоб вимагати від компіляторів для деяких загальних платформ генерувати більше коду, ніж вимагалося б за певніших специфікацій. Примус типу int i=129; signed char c=i;- це одна така поведінка. Порівняно небагато процесорів мають інструкцію, яка зробила б cрівним, iколи вона знаходиться в діапазоні від -127 до +127 і дала б будь-яке послідовне відображення інших значень iзначень у діапазоні від -128 до +127, що відрізнялося від скорочення двох-доповнення, або. ..
supercat

... послідовно підніме сигнал у таких випадках. Оскільки Стандарт вимагає, щоб реалізації або давали послідовне відображення, або послідовно піднімали сигнал, єдиними платформами, де Стандарт не залишив би місця для чогось іншого, ніж скорочення двох доповнень, були б такі речі, як DSP з насиченим арифметичним обладнанням. Що стосується історичної основи Undefined Behavior, я б сказав, що проблема не лише в апаратних платформах. Навіть на платформі, де переповнення поводилося б дуже послідовно, може бути корисним, щоб компілятор вловив це ...
supercat
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.