В чем разница между литералами и переменными в C (signed vs unsigned short ints)?

я видел следующий код в книге компьютерные системы: перспектива программиста, 2 / E. Это хорошо работает и создает желаемый результат. Вывод может быть объяснен разницей подписанного и неподписанного представлений.

#include<stdio.h>
int main() {
    if (-1 < 0u) {
        printf("-1 < 0un");
    }
    else {
        printf("-1 >= 0un");
    }
    return 0;
}

код выше дает -1 >= 0u, однако, следующий код, который должен быть таким же, как и выше, не! Другими словами,

#include <stdio.h>

int main() {

    unsigned short u = 0u;
    short x = -1;
    if (x < u)
        printf("-1 < 0un");
    else
        printf("-1 >= 0un");
    return 0;
}

доходность -1 < 0u. Почему это произошло? Я не могу объяснить этот.

обратите внимание, что я видел похожие вопросы как этой, но они не помогают.

PS. Как сказал @Abhineet, дилемма может быть решена путем изменения short to int. Но как объяснить это явление? Другими словами,-1 в 4 байта это 0xff ff ff ff и в 2 байт 0xff ff. Учитывая их как 2S-дополнение, которое интерпретируется как unsigned, они имеют соответствующие значения 4294967295 и 65535. Они оба не менее 0 и я думаю, что в обоих случаях на выходе должен быть -1 >= 0u, то есть x >= u.

образец вывода для него на маленькой системе Intel endian:

для краткости:

-1 < 0u
u =
 00 00
x =
 ff ff

для int:

-1 >= 0u
u =
 00 00 00 00
x =
 ff ff ff ff

4 ответов


приведенный выше код дает -1 >= 0u

все целочисленные литералы (числовое constansts) имеют вид и, следовательно, также со знаком. По умолчанию, они имеют тип int, который подписан. Когда вы добавляете u суффикс, вы превращаете литерал в unsigned int.

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

преобразование из signed в unsigned четко определено (6.3.1.3):

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

например, для 32-битных целых чисел на стандартных двух система дополнения, максимальное значение целого числа без знака -2^32 - 1 (4294967295, UINT_MAX в пределах.ч.) Один больше, чем максимальное значение 2^32. И -1 + 2^32 = 4294967295, так что буквальный -1 преобразуется в unsigned int со значением 4294967295. Который больше 0.


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

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

если short меньше, чем int на данный платформа (как и в случае с 32 и 64 битными системами), любая short или unsigned short поэтому всегда будет преобразовываться в int, потому что они могут поместиться внутри.

Итак, для выражения if (x < u), вы на самом деле в конечном итоге с if((int)x < (int)u) который ведет себя так, как ожидалось (-1 меньше 0).


вы сталкиваетесь с целочисленными правилами продвижения C.

операторы на типах меньше, чем int автоматически продвигать свои операнды к int или unsigned int. Более подробные объяснения см. В комментариях. Есть еще один шаг для бинарные (два операнда) оператора, если типы не совпадают после этого (например, беззнаковый Инт и Инт). Я не буду пытаться суммировать правила более подробно. см. ответ Лундина.

этот блог пост охватывает это более подробно, с аналогичным примером для вашего: подписанный и неподписанный символ. Он цитирует спецификацию C99:

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


вы можете играть с этим более легко на что-то вроде godbolt, С функцией, которая возвращает один или ноль. Просто посмотрите на выходные данные компилятора, чтобы увидеть, что происходит.

#define mytype short

int main() {
    unsigned mytype u = 0u;
    mytype x = -1;
    return (x < u);
}

кроме того , что вы, похоже, предполагаете, это не свойство определенной ширины типов, здесь 2 байта против 4 байтов, но вопрос о правилах, которые должны применяться. Целочисленные правила продвижения гласят, что short и unsigned short превращается в int на всех платформах, где соответствующий диапазон значений укладывается в int. Так как это имеет место здесь, оба значения сохраняются и получают тип int. -1 это прекрасно представимы в int как 0. Итак, результаты теста в -1 меньше, чем 0.

в случае проверки -1 против 0u общее преобразование выбрал unsigned тип, общий тип, к которому обращаются. -1 преобразовано в unsigned значение UINT_MAX, что превышает 0u.

это хороший пример, почему вы никогда не должны использовать "узкие" типы для арифметики или сравнения. Используйте их только если у вас размер север ограничение. Это редко будет иметь место для простых переменных, но в основном для больших массивов, где вы действительно можете получить от хранения в узком типе.


0u не unsigned short, это unsigned int.

Edit:: объяснение поведения, как выполняется сравнение ?

как ответил Йенс Gustedt,

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

в сущности то, что делает

если типы разные ширина (точнее какой стандарт calls conversion rank), то он преобразуется в более широкий тип, если оба типы имеют одинаковую ширину, кроме действительно странных архитектур, беззнаковое из них wins подписывается на беззнаковое преобразование значения -1 с любым типом всегда приводит к наивысшему представимому значению типа без знака.

более объяснительный блог, написанный им, можно найти здесь.