2010-01-18 6 views
33

См Этот фрагмент кодаоперация сравнения на неподписанных и подписанных целых

int main() 
{ 
unsigned int a = 1000; 
int b = -1; 
if (a>b) printf("A is BIG! %d\n", a-b); 
else printf("a is SMALL! %d\n", a-b); 
return 0; 
} 

Это дает выход: а мал: 1001

Я не понимаю, что здесь происходит. Как работает оператор> здесь? Почему «a» меньше «b»? Если это действительно меньше, почему я получаю положительное число (1001) как разницу?

+2

Если вы используете флаг компилятора -Wsign-compare, вы получите предупреждение для сравнения. Вы всегда должны использовать -Wall (который включает -Wsign-compare). См. [Здесь] (http://www.a-coding.com/2010/12/beware-usigned-integers.html) для других способов избежать этой проблемы. – Aleph7

+0

Смотрите это сообщение для получения дополнительной информации: http://stackoverflow.com/q/10474769/844882 –

+1

@ Aleph7 - технически это не совсем правильно, -Wsign-compare - это только inc. с -Wall * if * вы компилируете C++. Он не включен для C (см. Здесь https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html). Я протестировал его и могу подтвердить, что код выше не дает предупреждения в -Wall, но делает с -Wsign-compare (я использую gcc (Ubuntu 5.2.1-22ubuntu2) 5.2.1 20151010) – bph

ответ

43

Бинарные операции между различными интегральными типами выполняются в рамках «общего» типа, определенного с помощью так называемых обычных арифметических преобразований (см спецификации языка, 6,3 .1.8). В вашем случае «общий» тип - unsigned int. Это означает, что операнд int (ваш b) будет преобразован в unsigned int перед сравнением, а также с целью выполнения вычитания.

Когда -1 преобразуются в unsigned int результате является максимально возможным значением unsigned int (таким же, как UINT_MAX). Излишне говорить, что это будет больше, чем ваше неподписанное значение 1000, что означает, что a > b действительно ложно, а a действительно маленький по сравнению с (unsigned) b. if в вашем коде должен быть разрешен к ответвлению else, что и наблюдалось в вашем эксперименте.

Те же правила преобразования применяются к вычитанию. Ваш a-b действительно интерпретирован как a - (unsigned) b, и результат имеет тип unsigned int. Такое значение не может быть напечатано с помощью спецификатора формата %d, поскольку %d работает только с , подписанными знаками. Ваша попытка распечатать его с помощью %d приводит к неопределенному поведению, поэтому значение, которое вы видите напечатанным (даже если оно имеет логическое детерминированное объяснение на практике), совершенно не имеет смысла с точки зрения языка C.

Редактировать: На самом деле, я могу ошибаться в неопределенной части поведения. Согласно спецификации языка C, общая часть диапазона соответствующего типа с подписью и без знака должна иметь идентичное представление (подразумевая, согласно сноске 31, «взаимозаменяемость в качестве аргументов для функций»).Таким образом, результат выражения a - b является неподписанным 1001, как описано выше, и если я что-то не упускаю, законно печатать это значение без знака с помощью спецификатора %d, так как оно попадает в положительный диапазон от int. Печать (unsigned) INT_MAX + 1 с %d будет не определена, но 1001u в порядке.

+0

Хотя мы знаем или можем достаточно догадываться о вызывающем соглашении его реализации, чтобы сделать вывод о том, что произошло то, что был получен беззнаковый результат ab, который фактически равен 1001 через varargs unscathed и переинтерпретируется как подписанный без изменения значения. –

+1

Да, передача без знака int и выполнение 'va_arg (ap, int)' само по себе еще не UB. Но на самом деле UB нарушает требования printf в ожидании 'int'. Это звучит глупо для меня. Почему они не указали для printf: «Тип следующего аргумента должен быть подписанным или unsigned int и находиться в пределах диапазона int». –

+0

@Johannes: На самом деле, это может быть уже указано. См. Мое редактирование. – AnT

0

Вы делаете беззнаковое сравнение, т.е. сравнения от 1000 до 2^32 - 1.

Выход подписан из-% D в Printf.

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

+1

Вычитание не делает ничего подписанного. Вычитание - это та же операция для значений как подписанных, так и неподписанных. – wj32

+1

Неправильное вычитание между операндами 'int' и' unsigned int' оценивается как беззнаковое вычитание, и результат, конечно же, беззнаковый. – AnT

+0

AndreyT прав, реальная проблема - это% d в printf –

13

На типичной реализации, где int является 32-битным, -1, когда преобразуется в unsigned int является 4294967295, который действительно ≥ 1000.

Даже если вы относитесь к вычитанию в unsigned мире, 1000 - (4,294,967,295) = -4,294,966,295 = 1,001 который что вы получить.

Именно поэтому gcc будет плевать предупреждение при сравнении unsigned с signed. (Если вы не видите предупреждение, передать -Wsign-compare флаг.)

+0

Спасибо за ответ .., который дал понять – Gitmo

+1

Я проигнорировал из-за «4 294 967 295 (дополнение 2)». Это не имеет никакого отношения к дополнению 2. Это даст такое же значение на машине с дополнением 1. И даст другое значение для другого целочисленного битрейта. –

+0

@Schaub: Может быть, я не совсем понимаю, но я имею в виду, что 4 294 967 295 (что является дополнением 2 к 1) действительно ≥1. Кроме того, машина с дополнением 1 представляет представление -1 - 4 294 967 294. – kennytm

0

Найти простой способ сравнения, возможно, полезно, когда вы не можете избавиться от объявления без знака (например, [NSArray count]), просто принудительно введите «unsigned int» в «int».

Пожалуйста, исправьте меня, если я ошибаюсь.

if (((int)a)>b) { 
    .... 
} 
0

Аппаратное обеспечение предназначено для сравнения подписанных и подписанных без знака.

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

И -1 представлен как 1111..1111, поэтому это очень большое количество ... Самое большое ... Когда интерпретируется как неподписанное.

+0

Аппаратное обеспечение может не иметь никакой функции арифметического сравнения (как отдельной команды сравнения или вычитания) вообще, и все же такое аппаратное обеспечение все еще может использоваться для запуска программ на C. Аппаратное обеспечение важно, но выходит за рамки языка программирования, как определено стандартом языка. –