Ответ 1
TL; DR:
-
char c; c = getchar();
является неправильным, сломанным и багги. -
int c; c = getchar();
является правильным.
Это относится также к getc
и fgetc
, если не более того, потому что часто читается до конца файла.
Всегда сохраняйте возвращаемое значение getchar
(fgetc
, getc
...) (и putchar
) изначально в переменную типа int
.
Аргументом для putchar
может быть любой из int
, char
, signed char
или unsigned char
; его тип не имеет значения, и все они работают одинаково, хотя можно привести к положительным и другим отрицательным целым числам, переданным для символов выше и включая \200
(128).
Причина, по которой вы должны использовать int
для хранения возвращаемого значения как getchar
и putchar
заключается в том, что, когда достигнуто условие конца файла (или возникает ошибка ввода-вывода), оба из них возвращают значение макроса EOF
которое - отрицательная целочисленная константа (обычно -1
).
Для getchar
, если возвращаемое значение не является EOF
, это прочитанный unsigned char
zero-extended для int
. То есть, принимая 8-битные символы, возвращаемые значения могут быть 0
... 255
или значение макроса EOF
; опять же, принимая 8-битный символ, нет возможности сжать эти 257 различных значений на 256, чтобы каждый из них можно было идентифицировать однозначно.
Теперь, если вы сохранили его в char
вместо этого, эффект будет зависеть от того, подписан ли тип символа или без знака по умолчанию ! Это зависит от компилятора и компилятора, архитектуры и архитектуры. Если char
подписан и предполагается, что EOF
определяется как -1
, то как EOF
и символ '\377'
на входе будут сравниваться с EOF
; они будут расширены до (int)-1
.
С другой стороны, если char
без знака (по умолчанию это относится к ARM-процессорам, включая PI-системы Raspberry, и, похоже, это справедливо и для AIX), нет значения, которое может быть сохранено в c
, которое сравнивалось бы равным -1
; включая EOF
; вместо того, чтобы вырваться на EOF
, ваш код выведет один символ \377
.
Опасность здесь заключается в том, что с подписанным char
код, кажется, работает правильно, хотя он все еще ужасно нарушен - одно из значений входного права интерпретируется как EOF
. Кроме того, C89, C99, C11 не дает значения для EOF
; он только говорит, что EOF
является отрицательной целочисленной константой; поэтому вместо -1
можно было бы сказать -224
о конкретной реализации, что приведет к тому, что пространства будут вести себя как EOF
.
gcc
имеет переключатель -funsigned-char
который может использоваться для создания char
без знака на тех платформах, на которых он по умолчанию подписан:
% cat test.c
#include <stdio.h>
int main(void)
{
char c;
printf("Enter characters : ");
while((c= getchar()) != EOF){
putchar(c);
}
return 0;
}
Теперь мы запускаем его со знаком char
:
% gcc test.c && ./a.out
Enter characters : sfdasadfdsaf
sfdasadfdsaf
^D
%
Кажется, работает правильно. Но с unsigned char
:
% gcc test.c -funsigned-char && ./a.out
Enter characters : Hello world
Hello world
���������������������������^C
%
То есть, я попытался нажать Ctrl-D
там много раз, но было напечатано для каждого EOF
вместо разрыва токовой циклы.
Теперь, опять же, для подписанного случая char
он не может отличить char
255 и EOF
от Linux, разбивая его на двоичные данные и так:
% gcc test.c && echo -e 'Hello world\0377And some more' | ./a.out
Enter characters : Hello world
%
Только первая часть до выхода \0377
была записана на стандартный вывод.
Помните, что сравнение между символьными константами и int
содержащим знаковое значение без знака, может работать не так, как ожидалось (например, символьная константа 'ä'
в ISO 8859 -1 будет означать подписанное значение -28
. -28
что вы пишете код, который прочитайте ввод до 'ä'
в кодировке ISO 8859 -1, вы бы сделали
int c;
while((c = getchar()) != EOF){
if (c == (unsigned char)'ä') {
/* ... */
}
}
Из-за целочисленного продвижения все значения char
вписываются в int
и автоматически рекламируются при вызове функций, поэтому вы можете указать любой из char
int
, char
, signed char
или unsigned char
для putchar
в качестве аргумента (не хранить его возвращаемое значение), и это будет работать, как ожидалось.
Фактическое значение, переданное в целые числа, может быть положительным или даже отрицательным; например, символьная константа \377
будет отрицательной в 8-битовой системе char, где char
подписан; однако putchar
(или fputc
фактически) передаст значение в unsigned char. C11 7.21.7.3p2:
2 Функция fputc записывает символ, указанный
c
(преобразованный в unsigned char) в выходной поток, на который указывает поток [...]
(акцент мой)
Т.е. fputc
будет гарантированно преобразовать заданный c
как если бы (unsigned char)c