Преобразование типа - без знака для подписанного int/char

Я попробовал выполнить следующую программу:

#include <stdio.h>

int main() {
    signed char a = -5;
    unsigned char b = -5;
    int c = -5;
    unsigned int d = -5;

    if (a == b)
        printf("\r\n char is SAME!!!");
    else
        printf("\r\n char is DIFF!!!");

    if (c == d)
        printf("\r\n int is SAME!!!");
    else
        printf("\r\n int is DIFF!!!");

    return 0;
}

Для этой программы я получаю вывод:

char - ДИФФ!!! int is SAME!!!

Почему мы получаем разные результаты для обоих?
Должен ли выход быть ниже?

char ТАКОЕ!!! int is SAME!!!

A ссылка на кодовую ссылку.

Ответ 1

Это из-за различных неявных правил преобразования типов в C. Есть два из них, которые программист C должен знать: обычные арифметические преобразования и целые рекламные акции (последние являются частью первого).

В случае char у вас есть типы (signed char) == (unsigned char). Это и малые целые типы. Другими такими малыми целыми типами являются bool и short. В целых правилах продвижения указано, что всякий раз, когда маленький целочисленный тип является операндом операции, его тип будет повышаться до int, который будет подписан. Это произойдет независимо от того, был ли тип подписан или неподписан.

В случае signed char знак будет сохранен, и ему будет присвоено значение int, содержащее значение -5. В случае unsigned char оно содержит значение, равное 251 (0xFB). Ему будет присвоено значение int, содержащее то же значение. В итоге вы получите

if( (int)-5 == (int)251 )

В целочисленном случае у вас есть типы (signed int) == (unsigned int). Они не являются маленькими целыми типами, поэтому целые рекламные акции не применяются. Вместо этого они уравновешиваются обычными арифметическими преобразованиями, которые утверждают, что если два операнда имеют одинаковый "ранг" (размер), но различную подпись, подписанный операнд преобразуется в тот же тип, что и без знака. В итоге вы получите

if( (unsigned int)-5 == (unsigned int)-5)

Ответ 2

Прохладный вопрос!

Сравнение int работает, потому что оба int содержат точно одинаковые биты, поэтому они по существу одинаковы. Но как насчет char s?

Ah, C неявно продвигает char до int в различных случаях. Это одна из них. Ваш код говорит if(a==b), но то, что на самом деле делает компилятор, это:

if((int)a==(int)b) 

(int)a - -5, но (int)b - 251. Это определенно не то же самое.

РЕДАКТИРОВАТЬ: Как указывал @Carbonic-Acid, (int)b равен 251, только если a char имеет длину 8 бит. Если int имеет длину 32 бита, (int)b - -32764.

REDIT: есть целая куча комментариев, обсуждающих характер ответа, если байт не 8 бит. Единственное отличие в этом случае состоит в том, что (int)b не 251, а другое положительное число, которое не является -5. Это не имеет отношения к вопросу, который все еще очень крут.

Ответ 3

Добро пожаловать в цельное продвижение. Если я могу процитировать с сайта:

Если int может представлять все значения исходного типа, это значение равно преобразован в int; в противном случае он преобразуется в unsigned int. Они называются целыми акциями. Все остальные типы не меняются посредством целых рекламных акций.

C может быть действительно запутанным, когда вы делаете такие сравнения, я недавно озадачил некоторых моих друзей, не связанных с программированием, со следующим дразнением:

#include <stdio.h>
#include <string.h>

int main()
{
    char* string = "One looooooooooong string";

    printf("%d\n", strlen(string));

    if (strlen(string) < -1) printf("This cannot be happening :(");

    return 0;
}

Что действительно печатает This cannot be happening :( и, по-видимому, демонстрирует, что 25 меньше -1!

Что происходит под этим, однако, это то, что -1 представляется как целое число без знака, которое из-за представления базовых битов равно 4294967295 на 32-битной системе. И, естественно, 25 меньше 4294967295.

Если мы, однако, явно применяем тип size_t, возвращаемый strlen как целое число со знаком:

if ((int)(strlen(string)) < -1)

Тогда он будет сравнивать 25 против -1, и все будет хорошо с миром.

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

Это особенно смущает программистов Java, поскольку все примитивные типы там подписаны. Здесь Джеймс Гослинг (один из создателей Java) должен был сказать по этому вопросу:

Гослинг: для меня, как для языкового дизайнера, которого я действительно не считаю как и в наши дни, то, что "простое" действительно оказалось Я ожидаю, что J. Random Developer проведет спецификацию в его голове. Что определение говорит, что, например, Java не является - и на самом деле много на этих языках заканчивается множество угловых дел, что никто действительно понимает. Опробуйте любого разработчика C о неподписанном, и довольно скоро вы обнаружите, что почти ни один разработчик C не понимает, что продолжается без знака, какая неподписанная арифметика. Такие вещи сделанный комплекс С. Языковая часть Java, я думаю, довольно проста. Библиотеки, которые вам нужно найти.

Ответ 4

Шестнадцатеричное представление -5:

  • 8-бит, два дополнения signed char: 0xfb
  • 32-бит, два дополнения signed int: 0xfffffffb

Когда вы конвертируете подписанный номер в беззнаковый номер или наоборот, компилятор делает... точно ничего. Чем там можно заняться? Число является либо конвертируемым, либо нет, и в этом случае следует поведение undefined или поведение, определяемое реализацией (я фактически не проверял, какой), и наиболее эффективное поведение, определяемое реализацией, - ничего не делать.

Итак, шестнадцатеричное представление (unsigned <type>)-5:

  • 8-бит, unsigned char: 0xfb
  • 32-бит, unsigned int: 0xfffffffb

Посмотрите знакомый? Они бит-бит-бит такие же, как и подписанные версии.

Когда вы пишете if (a == b), где a и b имеют тип char, то, что компилятор действительно должен читать, это if ((int)a == (int)b). (Это то, что "целое продвижение", о котором все говорят.)

Итак, что происходит, когда мы конвертируем char в int?

  • 8-бит signed char до 32-бит signed int: 0xfb0xfffffffb
    • Ну, это имеет смысл, потому что оно соответствует представлениям -5 выше!
    • Он называется "sign-extend", потому что он копирует верхний бит байта, "знаковый бит", влево в новое более широкое значение.
  • 8-бит unsigned char до 32-бит signed int: 0xfb0x000000fb
    • На этот раз он выполняет "нуль-расширение", потому что тип источника не имеет знака, поэтому нет скопированного знака.

Итак, a == b действительно 0xfffffffb == 0x000000fb = > не соответствует!

И, c == d действительно соответствует 0xfffffffb == 0xfffffffb = > !

Ответ 5

Моя точка зрения: разве вы не получили предупреждение во время компиляции "сравнение подписанного и неподписанного выражения"?

Компилятор пытается сообщить вам, что он имеет право делать сумасшедшие вещи!:) Я бы добавил, сумасшедшие вещи будут происходить с использованием больших значений, близких к емкости примитивного типа. И

 unsigned int d = -5;

однозначно присваивает значение d, оно эквивалентно (даже если, возможно, не гарантируется эквивалент):

 unsigned int d = UINT_MAX -4; ///Since -1 is UINT_MAX

Edit:

Однако интересно заметить, что только второе сравнение дает предупреждение (проверьте код). Поэтому это означает, что компилятор, применяющий правила преобразования, уверен, что в сравнении между unsigned char и char не будет ошибок (во время сравнения они будут преобразованы в тип, который может безопасно представлять все возможные значения). И он прав в этом вопросе. Затем он сообщает вам, что это не относится к unsigned int и int: во время сравнения один из 2 будет преобразован в тип, который не может полностью его представить.

Для полноты, Я также проверил его также на короткий: компилятор ведет себя так же, как и для символов, и, как и ожидалось, во время выполнения отсутствуют ошибки.

.

В связи с этой темой, я недавно спросил этот вопрос (еще, ориентированный на С++).