Przechowywanie znaku EOF (End of File) w postaci znaku
On 18 lutego, 2021 by admin Czytałem w książce Dennisa Ritchiego The C Programming Language książka, która int
musi być użyta dla zmiennej do przechowywania EOF –, aby była wystarczająco duża, aby mogła pomieścić wartość EOF – nie char
. Ale poniższy kod działa dobrze:
#include<stdio.h> main() { char c; c=getchar(); while(c!=EOF) { putchar(c); c=getchar(); } }
Kiedy tam nie ma już danych wejściowych, getchar
zwraca EOF. W powyższym programie zmienna c
o typie char może ją przechowywać pomyślnie.
Dlaczego to działa? Zgodnie z wyjaśnieniem we wspomnianej książce kod nie powinien działać.
Komentarze
Odpowiedź
Wygląda na to, że Twój kod działa, ponieważ niejawne konwersje typów przypadkowo działają prawidłowo.
getchar()
zwraca int
o wartości mieszczącej się w zakresie unsigned char
lub EOF
( która musi być ujemna, zwykle jest to -1). Zauważ, że EOF
samo w sobie nie jest znakiem, ale sygnałem, że nie ma więcej dostępnych znaków.
Podczas zapisywania wyniku z getchar()
w c
istnieją dwie możliwości. Wartość może reprezentować typ char
, w którym to przypadku jest to wartość c
. Lub typ char
nie może reprezentować wartości. W takim przypadku nie jest określone, co się stanie. Procesory Intela po prostu odcinają wysokie bity, które nie pasują do nowego typu (skutecznie zmniejszając wartość modulo 256 dla char
), ale nie powinieneś na tym polegać.
Następnym krokiem jest porównanie c
z EOF
. Ponieważ EOF
jest int
, c
również zostanie przekonwertowany na int
, zachowując zapisaną wartość w c
. Jeśli c
może przechowywać wartość EOF
, porównanie się powiedzie , ale jeśli c
może nie zapisać wartości, porównanie zakończy się niepowodzeniem, ponieważ podczas konwersji aby wpisać char
.
Wygląda na to, że Twój kompilator wybrał char
ze znakiem i wartością EOF
small wystarczy, aby zmieścić się w char
. Jeśli char
były bez znaku (lub jeśli użyłeś unsigned char
), test nie powiódłby się, ponieważ unsigned char
nie może „t przechowywać wartości EOF
.
Zauważ również, że jest drugi problem z twoim kodem. Jako EOF
sam w sobie nie jest znakiem, ale wymuszasz na nim typ char
, bardzo prawdopodobne jest, że istnieje znak, który zostanie błędnie zinterpretowany jako EOF
i dla połowy możliwych znaków jest niezdefiniowane, jeśli zostaną przetworzone poprawnie.
Komentarze
- Wymuszanie aby wpisać
char
wartości spoza zakresuCHAR_MIN
..CHAR_MAX
jest wymagana wola wartość zdefiniowana w implementacji, daje wzorzec bitowy, który implementacja określa jako reprezentację pułapki, lub podnosi sygnał zdefiniowany w implementacji. W większości przypadków implementacje musiałyby wykonać dużo dodatkowej pracy, aby zrobić cokolwiek innego niż dwie ' redukcje dopełniacza s.Jeśli osoby z Komitetu Normalizacyjnego zgodziłyby się z ideą, że kompilatory powinny być zachęcane do wdrażania zachowań zgodnych z zachowaniami większości innych kompilatorów, przy braku powodów, aby postąpić inaczej … - … uznałbym takie przymus jako niezawodny (nie mówiąc, że kod nie powinien ' dokumentować swoich zamiarów, ale
(signed char)x
powinien być uważany za jaśniejszy i tak samo bezpieczny jako((unsigned char)x ^ CHAR_MAX+1))-(CHAR_MAX+1)
.) W obecnej sytuacji nie ' nie widzę prawdopodobieństwa, że kompilatory zaimplementują jakiekolwiek inne zachowanie zgodne z dzisiejszym ' s standard; jedynym niebezpieczeństwem byłoby to, że norma mogłaby zostać zmieniona, aby przerwać zachowanie w rzekomym interesie " optymalizacji ". - @supercat: Standard jest napisany w taki sposób, że żaden kompilator nie musi tworzyć kodu, którego zachowanie nie jest naturalnie obsługiwane przez procesor, do którego jest przeznaczony. Większość niezdefiniowanych zachowań ma miejsce, ponieważ (w czasie pisania standardu) nie wszystkie procesory zachowywały się konsekwentnie. Wraz ze wzrostem dojrzałości kompilatorów, twórcy kompilatorów zaczęli wykorzystywać niezdefiniowane zachowanie do bardziej agresywnych optymalizacji.
- Historycznie, intencją standardu było głównie to, co opisujesz, chociaż standard opisuje niektóre zachowania w wystarczająco szczegółowe, aby wymagać od kompilatorów dla niektórych popularnych platform, aby generowały więcej kodu niż byłoby to wymagane w przypadku luźniejszej specyfikacji. Jednym z takich zachowań jest wymuszenie typu w
int i=129; signed char c=i;
. Stosunkowo niewiele procesorów ma instrukcję, która sprawiłaby, żec
jest równei
, gdy ' jest w zakres od -127 do +127 i zapewni spójne mapowanie innych wartościi
na wartości z zakresu od -128 do +127, które różnią się od dwóch ' redukcja s-dopełniacza lub … - … konsekwentnie podniosłaby sygnał w takich przypadkach. Ponieważ Standard wymaga, aby implementacje albo zapewniały spójne mapowanie, albo konsekwentnie generowały sygnał, jedynymi platformami, na których Standard pozostawiłby miejsce na coś innego niż dwie ' redukcję s-dopełnienia, byłyby rzeczy jak DSP ze sprzętem arytmetyczno-nasycającym. Jeśli chodzi o historyczne podstawy niezdefiniowanego zachowania, powiedziałbym, że problem nie dotyczy tylko ' platform sprzętowych. Nawet na platformie, na której przepełnienie zachowywałoby się w bardzo spójny sposób, przydatne może być ustawienie pułapki przez kompilator …
0xff
. Przechowywanie wynikugetchar()
wint
rozwiązuje ten problem. Twoje pytanie jest zasadniczo takie samo, jak pytanie 12.1 w comp.lang.c FAQ , który jest doskonałym źródłem. (Ponadtomain()
powinno byćint main(void)
i nie byłoby 'return 0;
przed zamykającym}
.)