Почему 0 <-0x80000000?


253

У меня ниже простая программа:

#include <stdio.h>

#define INT32_MIN        (-0x80000000)

int main(void) 
{
    long long bal = 0;

    if(bal < INT32_MIN )
    {
        printf("Failed!!!");
    }
    else
    {
        printf("Success!!!");
    }
    return 0;
}

Условие if(bal < INT32_MIN )всегда верно. Как это возможно?

Он отлично работает, если я изменю макрос на:

#define INT32_MIN        (-2147483648L)

Кто-нибудь может указать на проблему?


3
Сколько стоит CHAR_BIT * sizeof(int)?
5gon12eder

1
Вы пробовали распечатать бал?
Райан Фитцпатрик

10
ИМХО более интересно то, что это верно только для -0x80000000, но неверно для -0x80000000L, -2147483648и -2147483648L(GCC 4.1.2), поэтому возникает вопрос: почему это ИНТ буквальные -0x80000000отличается от Int литерала -2147483648?
Андреас Фестер

2
@Bathsheba Я только что запустил программу на онлайн-компиляторе tutorialspoint.com/codingground.htm
Jayesh Bhoi

2
Если вы когда-либо замечали, что (некоторые воплощения) <limits.h>определяют INT_MINкак (-2147483647 - 1), теперь вы знаете почему.
Звол

Ответы:


363

Это довольно тонко.

Каждый целочисленный литерал в вашей программе имеет тип. Какой тип он имеет, регулируется таблицей в 6.4.4.1:

Suffix      Decimal Constant    Octal or Hexadecimal Constant

none        int                 int
            long int            unsigned int
            long long int       long int
                                unsigned long int
                                long long int
                                unsigned long long int

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

  • Пытаться int
  • Если это не подходит, попробуйте long
  • Если это не подходит, попробуйте long long.

Шестнадцатеричные литералы ведут себя иначе! Если литерал не может вписаться в подписанный тип, например int, он сначала попытается, unsigned intпрежде чем перейти к использованию более крупных типов. Смотрите разницу в таблице выше.

Так что в 32-битной системе ваш литерал 0x80000000имеет тип unsigned int.

Это означает, что вы можете применить унарный -оператор к литералу, не вызывая поведение, определяемое реализацией, как это было бы при переполнении целого числа со знаком. Вместо этого вы получите значение 0x80000000, положительное значение.

bal < INT32_MINвызывает обычные арифметические преобразования и результат выражения 0x80000000повышается с unsigned intдо long long. Значение 0x80000000сохраняется и 0 меньше 0x80000000, отсюда и результат.

Когда вы заменяете литерал на 2147483648Lвас, используйте десятичную нотацию, и поэтому компилятор не выбирает unsigned int, а пытается разместить его внутри long. Также суффикс L говорит, что вы хотите, long если это возможно . Суффикс L на самом деле имеет аналогичные правила, если вы продолжаете читать упомянутую таблицу в 6.4.4.1: если число не соответствует запрошенному long, чего не происходит в 32-битном случае, компилятор даст вам, long longгде он подойдет просто отлично.


3
«... замените литерал на -2147483648L, который вы явно получите long, который подписан». Хммм, В 32-битной longсистеме 2147483648L, не будет соответствовать в long, поэтому она становится long long, то- применяется - или так я думал.
chux - Восстановить Монику

2
@ASH Потому что тогда максимальное число, которое может иметь int 0x7FFFFFFF. Попробуйте сами:#include <limits.h> printf("%X\n", INT_MAX);
Лундин

5
@ASH Не путайте шестнадцатеричное представление целочисленных литералов в исходном коде с базовым двоичным представлением числа со знаком. Литерал 0x7FFFFFFFпри написании в исходном коде всегда является положительным числом, но ваша intпеременная может, конечно, содержать необработанные двоичные числа вплоть до значения 0xFFFFFFFF.
Лундин

2
@ASH вызывает ìnt n = 0x80000000преобразование литерала без знака в тип со знаком . Что произойдет, зависит от вашего компилятора - это поведение, определяемое реализацией. В этом случае он решил показать весь литерал в int, перезаписывая бит знака. В других системах может быть невозможно представить тип, и вы вызываете неопределенное поведение - программа может аварийно завершить работу. Вы получите то же самое поведение, если вы сделаете int n=2147483648;это, оно вообще не связано с шестнадцатеричной нотацией.
Лундин

3
Объяснение того, как унарные -применяются к целым числам без знака, можно немного расширить. Я всегда предполагал (хотя, к счастью, никогда не полагался на предположение), что значения без знака будут «повышены» до значений со знаком, или, возможно, результат будет неопределенным. (Честно говоря, это должна быть ошибка компиляции; что вообще - 3uзначит?)
Кайл Стрэнд

27

0x80000000является unsignedлитералом со значением 2147483648.

Применение унарного минуса к этому все еще дает вам тип без знака с ненулевым значением. (На самом деле, для ненулевого значения xзначение, которое вы в итоге получите, равно UINT_MAX - x + 1.)


23

Этот целочисленный литерал 0x80000000имеет тип unsigned int.

Согласно стандарту C (6.4.4.1 Целочисленные константы)

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

И эта целочисленная константа может быть представлена ​​типом unsigned int.

Так что это выражение

-0x80000000имеет тот же unsigned intтип. Кроме того, он имеет то же значение 0x80000000в представлении дополнения двух, которое рассчитывает следующим образом

-0x80000000 = ~0x80000000 + 1 => 0x7FFFFFFF + 1 => 0x80000000

Это имеет побочный эффект, если написать, например,

int x = INT_MIN;
x = abs( x );

Результат будет снова INT_MIN.

Таким образом, в этом состоянии

bal < INT32_MIN

там сравнивается 0с беззнаковое значение 0x80000000преобразуется к типу INT долго долго в соответствии с правилами обычных арифметических преобразований.

Очевидно, что 0 меньше, чем 0x80000000.


12

Числовая константа 0x80000000имеет тип unsigned int. Если мы возьмем -0x80000000и сделаем 2s комплимент по математике, мы получим это:

~0x80000000 = 0x7FFFFFFF
0x7FFFFFFF + 1 = 0x80000000

Так -0x80000000 == 0x80000000. И сравнение (0 < 0x80000000)(так 0x80000000как без знака) верно.


Это предполагает 32-битный intс. Хотя это очень распространенный выбор, в любой конкретной реализации он intможет быть либо уже, либо шире. Однако это правильный анализ для этого случая.
Джон Боллингер

Это не относится к коду OP, -0x80000000арифметика без знака. ~0x800000000это другой код.
ММ

Мне кажется, это лучший и правильный ответ. @ ММ, он объясняет, как взять двойное дополнение. Этот ответ конкретно касается того, что отрицательный знак делает с числом.
Осьминог

@ Осьминог отрицательный знак не применяет дополнение 2 к числу (!) Хотя это кажется очевидным, оно не описывает, что происходит в коде -0x80000000! На самом деле дополнение 2 не имеет никакого отношения к этому вопросу полностью.
ММ

12

Когда мы думаем, что -это часть числовой константы, возникает путаница .

В приведенном ниже коде 0x80000000числовая константа. Его тип определяется только по этому. -Применяется после и не меняет тип .

#define INT32_MIN        (-0x80000000)
long long bal = 0;
if (bal < INT32_MIN )

Необработанные необработанные числовые константы являются положительными.

Если это десятичное, то тип присвоенный первый типа , который будет держать его: int, long, long long.

Если константа восьмеричный или шестнадцатеричный, он получает первый тип , который держит его: int, unsigned, long, unsigned long, long long, unsigned long long.

0x80000000, в системе ОП получает тип unsignedили unsigned long. В любом случае, это какой-то неподписанный тип.

-0x80000000также является некоторым ненулевым значением и является незнаковым типом, он больше 0. Когда код сравнивает это с a long long, значения не изменяются на 2 сторонах сравнения, так 0 < INT32_MINчто верно.


Альтернативное определение избегает этого любопытного поведения

#define INT32_MIN        (-2147483647 - 1)

Давайте прогуляемся по фэнтезийной земле на некоторое время, где intи unsigned48-битные.

Тогда 0x80000000вписывается intи так тип int. -0x80000000затем отрицательное число и результат распечатки отличается.

[Назад к реальному слову]

Поскольку 0x80000000вписывается в некоторый неподписанный тип перед подписанным типом, поскольку он просто больше, чем some_signed_MAXвнутри some_unsigned_MAX, это некоторый неподписанный тип.


8

C имеет правило, что целочисленный литерал может быть signedили unsignedзависит от того, подходит ли он signedили unsigned(целочисленное продвижение). На 32-битной машине литерал 0x80000000будет unsigned. 2-е дополнение -0x80000000находится 0x80000000 на 32-битной машине. Таким образом, сравнение bal < INT32_MINмежду signedи unsignedи перед сравнением в соответствии с правилом C unsigned intбудет преобразован в long long.

С11: 6.3.1.8/1:

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

Поэтому bal < INT32_MINвсегда есть true.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.