Почему 0 <-0x80000000?
У меня ниже простая программа:
#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)
Кто-нибудь может указать на проблему?
6 ответов
Это довольно тонко.
Каждый целочисленный литерал в вашей программе имеет тип. Какой тип у него регулируется таблицей в 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
type, он попытается использовать следующий больший тип, как указано в таблице выше. Так что для обычных десятичных целочисленных литералов это выглядит так:
- Пытаться
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
где это будет соответствовать просто отлично.
0x80000000
является unsigned
литерал со значением 2147483648.
Применение унарного минуса к этому все еще дает вам тип без знака с ненулевым значением. (На самом деле, для ненулевого значения x
, значение, которое вы в конечном итоге UINT_MAX - x + 1
.)
Это целое число буквальное 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
преобразуется в тип long long int по правилам обычных арифметических преобразований.
Очевидно, что 0 меньше 0x80000000
,
Путаница возникает в мышлении -
является частью числовой константы.
В приведенном ниже коде 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. Когда код сравнивает это с long long
значения не меняются на 2 сторонах сравнения, поэтому 0 < INT32_MIN
правда.
Альтернативное определение избегает этого любопытного поведения
#define INT32_MIN (-2147483647 - 1)
Давайте пойдем в сказочную страну, где int
а также unsigned
являются 48-битными.
затем 0x80000000
вписывается в int
и так тип int
, -0x80000000
затем отрицательное число, и результат распечатки отличается.
[Назад к реальному слову]
поскольку 0x80000000
вписывается в некоторый неподписанный тип перед подписанным типом, так как он больше, чем some_signed_MAX
еще внутри some_unsigned_MAX
Это какой-то неподписанный тип.
Числовая константа 0x80000000
имеет тип unsigned int
, Если мы возьмем -0x80000000
и сделать 2s комплимент на это, мы получим это:
~0x80000000 = 0x7FFFFFFF
0x7FFFFFFF + 1 = 0x80000000
Так -0x80000000 == 0x80000000
, И сравнивая (0 < 0x80000000)
(поскольку 0x80000000
без знака) верно.
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
,