Подтвердить что ты не робот

Разница между int и char в getchar/fgetc и putchar/fputc?

Я пытаюсь изучить C сам по себе, и я немного смущен с getchar и putchar:

1

#include <stdio.h>

int main(void)
{
    char c;
    printf("Enter characters : ");
    while((c = getchar()) != EOF){
      putchar(c);
    }
    return 0;
}

2

#include <stdio.h>

int main(void)
{
    int c;
    printf("Enter characters : ");
    while((c = getchar()) != EOF){
      putchar(c);
    }
    return 0;
}

Функция библиотеки C int putchar(int c) записывает символ (неподписанный char), указанный аргументом char в stdout.

Функция библиотеки C int getchar(void) получает символ (unsigned char) из stdin. Это эквивалентно getc с stdin в качестве аргумента.

Означает ли это, что putchar() принимает как int, так и char, или любой из них, и для getchar() следует использовать int или char?

4b9b3361

Ответ 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

Ответ 2

Всегда используйте int для сохранения символа из getchar(), поскольку константа EOF имеет тип int. Если вы используете char, то сравнение с EOF неверно.

Вы можете безопасно передать char на putchar(), хотя автоматически повышается до int.

Примечание: Технически использование char будет работать в большинстве случаев, но тогда у вас не может быть символ 0xFF, поскольку они будут интерпретироваться как EOF из-за преобразования типа. Чтобы охватить все случаи всегда, используйте int. Как сказал @Ilja - int необходимо представить все 256 возможных значений символов и EOF, что составляет всего 257 возможных значений, которые не могут быть сохранены в типе char.