#c #literals #signed #integer-overflow
#c #литералы #подписано #целое число-переполнение
Вопрос:
#include <stdio.h>
#include <math.h>
#include <limits.h>
int main(){
long long a=2147488648, b;
scanf("%lld", amp;a);
b=a*2;
scanf("%lld", amp;b);
printf("%lld", amp;b);
}
Я сделал вместо «long long» — «int» и вместо «%lld» — «%d», но эта проблема не исчезает
Комментарии:
1. Пожалуйста, не путайте C с C #, это очень разные языки
2.
printf("%lld", b);
3. @Cid, хотя это не имеет никакого отношения к предупреждению компилятора.
4. @Shawn Да, это решит проблему, но почему возникает предупреждение, важно ли это и что изменилось между C90 и следующей версией?
5. OP: Какой компилятор и опции к нему вы используете?
Ответ №1:
Основная проблема заключается в том, что 2 147 488 648 равно 2 ^ 31 5000. Битовый шаблон, использующий 32 бита, является 1000 0000 0000 0000 0001 0011 1000 1000
(среди прочего, установлен 32-й бит). В 32-битном представлении целых чисел с двумя дополнениями это, возможно, удивительно, большое отрицательное число, -2 147 478 648,1.
Это явно не входит в намерения программиста. Поэтому стандарты требуют, чтобы литерал, превышающий диапазон значений int , имел тип long int
or long long int
, в зависимости от того, что подходит первым, даже если литерал не l
ll
имеет суффикса or! 2 Если число слишком велико для обоих, поведение не определено, хотя реализации могут свободно предоставлять и использовать целочисленные типы большего размера. В частности, в соответствии с современными стандартами тип десятичного литерала никогда не является беззнаковым, если не имеет суффикса с u
.
Хотя у меня нет стандартной копии C90, а старые черновики кажутся недоступными, cppreference утверждает, что действительно правила изменились в C99, и это, вероятно, то, о чем предупреждает компилятор. Примечательно, что C99 ввел больший целочисленный тип, long long int
, и обязал, чтобы он мог, по крайней мере, представлять число 9223372036854775807. Правила для литеральных типов в C90, по-видимому, заключались в том, что литерал, подобный 2147488648, значение которого находится вне диапазона со знаком long int
, но внутри unsigned long int
диапазона be unsigned long int
, чтобы максимально следовать намерениям программиста. Причина, по которой это правило было отменено, заключается в том, что значения без знака могут иметь непреднамеренные последствия в выражениях, включающих целые числа со знаком: все значения сначала преобразуются в беззнаковые. Это нормально для 2147488648u 1
, но не для if(2147488648u > -1)
(-1 преобразуется в unsigned int 4294967295). Компиляторы обычно предупреждают о выражениях со смешанным знаком (хотя gcc нуждается в Wextra!). Демонстрация:
$ cat unsigned-mismatch.c amp;amp; gcc -Wall -Wextra -pedantic -o unsigned-mismatch unsigned-mismatch.c amp;amp; ./unsigned-mismatch
#include<stdio.h>
int main(void)
{
if(-1 < 2147488648u) { printf("-1 < 2147488648u is truen"); }
else { printf("Oops: -1 < 2147488648u is false?n"); }
}
unsigned-mismatch.c: In function 'main':
unsigned-mismatch.c:5:8: warning: comparison of integer expressions of different signedness: 'int' and 'unsigned int' [-Wsign-compare]
5 | if(-1 < 2147488648u) { printf("-1 < 2147488648u is truen"); }
| ^
Oops: -1 < 2147488648u is false?
В вашем случае вам не нужно беспокоиться о предупреждении компилятора. Во-первых, вы никогда не используете присвоенное значение, поэтому все, что с ним делает компилятор, в любом случае не имеет значения; во-вторых, преобразование целочисленного значения без знака в целочисленный тип со знаком, который может содержать значение, четко определено и работает, как ожидалось. Хорошей практикой является устранение всех предупреждений (и подавление неизбежных с помощью опции pragma или компилятора в качестве указания на то, что вы знали об этом и рассматривали его). В вашем случае вы бы просто добавили к литералу суффикс с ll
, чтобы сделать его подписанным long long int
, который легко содержит значение. a
long long int
также является a , поэтому он тоже может содержать значение, так что все в порядке.
1 Проблема заключается в том, что в 32-разрядном целом числе со знаком, использующем представление дополнения two, этот бит, бит с наибольшим значением, используется для указания знака значения. Если этот бит установлен, число отрицательное. В дополнении two -1 — это «все установленные биты», и мы вычитаем 1, как обычно, оттуда, пока не достигнем INT_MIN, где все биты исчезли, кроме знакового бита.
2 В C это может снова вызвать удивление, когда разрешение перегрузки неожиданно выбирает функцию, принимающую long long
аргумент, даже если буквальный аргумент имеет внешний вид простого int
, без какого-либо суффикса.
Комментарии:
1. 2147488648 не равен 2 ^ 31. Это 2 ^ 31 5000.
2. @EricPostpischil Ох. Верно.