Почему «unsigned int» НЕ отличается от EOF — может ли он хранить отрицательные значения?

#c

#c

Вопрос:

Я пытаюсь прочитать файл bitmap, байт за байтом, и у меня есть цикл, который выполняется до тех пор, пока не будет достигнут EOF. Для этого у меня есть переменная, объявленная как unsigned int , которая хранит каждый байт. Цикл останавливается, когда эта переменная равна EOF .

Интересный момент: если я объявляю свою переменную как unsigned int , она работает. Однако, если я объявляю свою переменную как unsigned short int , цикл выполняется вечно, потому что он никогда не находит EOF .

 #include <stdio.h>

int main()
{
    FILE *file;
    unsigned int currentByte;

    file = fopen("/home/stanley/Desktop/x.bmp", "rb");

    while ((currentByte = fgetc(file)) != EOF) {
        printf("%d n", currentByte);
    }

    fclose(file);
    return 0;
}
  

Приведенный выше код — это код, который я пишу. Если файл имеет размер 90B, на экране выводится 90 байт.

Однако, по какой-то причине, когда я меняю его на unsigned short int currentByte , цикл продолжает выполняться вечно. Это как если бы currentByte никогда не было равно EOF .

Я где-то читал, что EOF содержит отрицательное значение (-1). Но если EOF значение отрицательное, почему оно работает, когда я использую только unsigned int и почему оно выдает ошибку при использовании unsigned short int ? Теоретически, не должна ли проблема быть связана с unsigned самим short , а не с,,? Это unsigned, который не может хранить отрицательные значения.

Извините, если это очень глупый вопрос. Я пытаюсь лучше понять, как работают биты и байты, и некоторые концепции пока могут быть для меня странными.

Я компилирую его в следующей среде:

  • ОС: Ubuntu 18.04 x64
  • GCC: gcc (Ubuntu 7.3.0-27ubuntu1 ~ 18.04) 7.3.0

Заранее спасибо. 🙂

Ответ №1:

Если размер int больше размера short , то вы столкнетесь с этой проблемой.

Давайте предположим, что EOF имеет тип int и содержит значение -1. Для примера давайте также предположим, что это int 32-разрядное значение, в то время как short это 16-разрядное значение.

В этом случае, если fgetc возвращает EOF значение, оно будет иметь значение 0xFFFFFFFF, если принимать его за unsigned int . При сравнении его с EOF (type int ) целое число со знаком -1 будет преобразовано в значение без знака 0xFFFFFFFF. Эти два значения равны, поэтому сравнение работает так, как ожидалось.

Однако значение, EOF возвращаемое fgetc , принимаемое за unsigned short , будет иметь значение 0xFFFF. Поскольку размер unsigned short меньше размера int, при сравнении этого значения с EOF , unsigned short 0xFFFF будет преобразован в int со значением 0x0000FFFF (дополнительные цифры показаны для наглядности). Поскольку -1 не равно 0xFFFF для 32-разрядного значения, это сравнение всегда не равно, и цикл не остановится.

Тот факт, что fgetc возвращает int намек на то, что вы должны сохранить его как этот тип, поскольку в противном случае вы отбросите некоторую информацию или вызовете путаницу в сравнениях.

Комментарии:

1. Одна важная деталь заключается в том, что unsigned short она будет повышена до int по сравнению с EOF . Но из-за неподписанности is будет расширен до нуля и станет 0x0000FFFF . signed short Снова сработал бы, поскольку он был бы расширен по знаку и стал 0xFFFFFFFF

2. В вашем ответе не упоминаются «обычные арифметические преобразования». Если unsigned short меньше int , он будет преобразован в int и 0xFFFF != -1

3. Итак, из того, что я понял, первое, что происходит: переменные int принимают как отрицательные, так и положительные значения. Для работы с дополнением two он резервирует свой первый бит, чтобы указать, какой сигнал имеет число (положительный или отрицательный). Как только fgetc() возвращается -1 значение (которое было бы 11111111 в 8-разрядном числе) и преобразуется в unsigned, оно становится 255 . Правильно ли это понимание?

4. Учитывая, что понимание в последнем комментарии правильное, проблема возникает, когда мне нужно сохранить это 255 в short переменной (предположим, что в ней всего 4 бита ). Я не могу сохранить 255 в это, но 15 самое большее. Итак, в этом случае у меня было бы условие цикла a 15 != 255 , которое всегда верно. Как указано в этом и других ответах. Если оба понимания верны, то я определенно думаю, что наконец-то понял это! =)

Ответ №2:

Вы должны использовать тип, int чтобы соответствовать тому, что fgetc возвращает, а не unsigned int . Причина, по которой условие остановки цикла работает с unsigned int , заключается не в том, что значение всегда отрицательное, а в том, что, когда != оператор используется с unsigned и signed операндами одинакового ранга, оба получают повышение до unsigned перед сравнением. Присвоение EOF результата fgetc currentByte и продвижение EOF к unsigned дают одинаковый результат, и, таким образом, они сравниваются одинаково.

Комментарии:

1. Спасибо! Я бы никогда не подумал, что сравнение между unsigned и signed приведет к преобразованию обоих в unsigned . Где я могу найти информацию такого типа, чтобы узнать больше подробностей о C?

Ответ №3:

Когда вы преобразуете целое число со знаком в целое число без знака (что происходит, когда EOF присваивается целочисленной переменной без знака), результат преобразуется в целое число без знака путем добавления UINT_MAX 1 . Итак, если EOF есть -1 , это значение становится UINT_MAX .

И UINT_MAX может правильно вписываться только в unsigned int и не unsigned short . И результат этого конкретного преобразования определяется реализацией, и поэтому поведение программы будет зависеть от него.

Обратите внимание, что fgetc функция возвращает int , поэтому вы должны использовать int переменную для сохранения ее значения.

Комментарии:

1. Ну, в этом случае это «следует использовать» для, unsignef int поскольку unsigned int это работает без какой-либо неопределенности или определенного реализацией поведения, которое заметно отличалось бы от int