8

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

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

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

#include <stdio.h> 

int main() { 

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

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

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

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

Выходной образец для него на небольшой Endian системы Intel:

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

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

Для междунар:

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

[Аналогичный вопрос] (http://stackoverflow.com/questions/17312545/type-conversion-unsigned-to-signed-int-char). – Lundin

+4

C ведет себя в терминах * значений *, а не представлений. Все материалы о дополнении 2 и ffff и 65535 и т. Д. Не имеют значения. –

+0

Не используйте форматирование кода для текста, который не является кодом. – EJP

ответ

10

Код выше урожайности -1> = 0u

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

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

Преобразование из подписанного в беззнаковое корректно определено (6.3.1.3):

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

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


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

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

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

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

+0

Спасибо. Это объясняет случай. Однако, интересно, почему дизайнеры решили таким образом? У тебя есть идеи? –

+2

@AliShakiba: Когда вы сталкиваетесь с различными операндами, вы можете либо решить, что вы будете конвертировать оба операнда в 'signed', или оба операнда в' unsigned', прежде чем продолжить сравнение. Поскольку 'signed int' является типом« по умолчанию », использование литерала' unsigned' для одного из операндов подразумевает, что у вас была причина указать этот дополнительный квалификатор. И так как C был спроектирован так, чтобы быть «близким к оборудованию», естественно попытаться вместить все в собственный размер слова на платформе, чтобы иметь возможность использовать соответствующие инструкции (которые не «смешивают» типы операндов и, как правило, размерные операнды). – Groo

+2

@AliShakiba Первоначальное обоснование целочисленного продвижения было чем-то вроде: если у вас есть, например, 'char x = 200, char y = 200;', а затем сделать 'x + y', то выражение не будет переполняться. Целочисленное продвижение - это несогласованность типов на языке C, хотя это и принесло гораздо больше вреда, чем пользы на протяжении многих лет, потому что молчаливые неявные рекламные ошибки намного труднее найти, чем простые ошибки переполнения целого числа. Также неявные правила продвижения по типу несколько сложны, и поэтому есть много программистов на С, которые не знают, как они работают, что является неудачным. – Lundin

0

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

Edit :: Объяснение поведения, How comparison is performed ?

Как ответил Jens Gustedt,

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

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

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

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

+0

Спасибо @Abhineet. Это верно. Однако мне любопытно, почему это произошло? '-1' в 4 байтах -' 0xff ff ff ff', а в 2 байтах - '0xff ff'. Учитывая их как 2s-дополнение, которые интерпретируются как 'unsigned', они имеют соответствующие значения' 4294967295' и '65535'. Оба они не меньше, чем '0', и я думаю, что в обоих случаях вывод должен быть' -1> = 0u', то есть 'x> = u'. –

+1

Это не отвечает на вопрос и не объясняет, почему переменные 'int' дают другой результат из« коротких »переменных. –

3

Вы используете целые правила продвижения C.

Операторы по типам, меньшим, чем int, автоматически рекламируют свои операнды до int или unsigned int. См. Комментарии для более подробных объяснений. Существует еще один шаг для двоичных (двух операндов) операторов, если типы по-прежнему не совпадают после этого (например, unsigned int vs. int). Я не буду пытаться суммировать правила более подробно, чем это. См. Ответ Лундина.

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

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


Вы можете играть вокруг с этим легче на что-то вроде godbolt, with a function that returns one or zero. Просто посмотрите на выход компилятора, чтобы узнать, что происходит.

#define mytype short 

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

Это хороший момент. Однако, как показано в примере в конце вопроса, 'short' и' unsigned short' являются двумя байтами, но с разными интерпретациями. Спасибо за ссылку. –

+0

@AliShakiba: Правила для целостного продвижения могут быть неинтуитивными. Это * - причина, по которой вы получаете разные результаты с короткими vars (оба получают повышение до int) против int vars (int не может представлять все возможные неподписанные ints). –

+3

На самом деле существует два набора правил: «целые рекламные акции» способствуют как «короткому», так и «неподписанному короткому» символу 'int' (на этой платформе), а« обычные арифметические преобразования »способствуют« int »для' unsigned' когда он сравнивается с 'unsigned'.Большинство операторов будут выполнять «целые акции», а затем «обычные арифметические преобразования». Заметными исключениями являются операторы сдвига битов, которые выполняют только целые рекламные акции. –

2

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

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

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

+0

Хранение данных в узких типах массивов отлично. Загрузка значений из массива в узкие локальные переменные обычно не хороша. Загрузка значений массива в локальные переменные 'int' позволяет избежать проблем с целыми правилами продвижения; просто обычные правила для подписанного vs. unsigned int. x86 по крайней мере имеет эффективные инструкции, которые подписывают int8_t или int16_t «на лету» при загрузке из памяти в регистр. IDK о ARM или других важных архитектурах. –