问题描述
#include <stdio.h>
#include <math.h>
#include <limits.h>
int main(){
long long a=2147488648,b;
scanf("%lld",&a);
b=a*2;
scanf("%lld",&b);
printf("%lld",&b);
}
我改为“long long” - “int” 和 “%lld” - “%d” 但这个问题并没有消失
解决方法
根本问题是 2,147,488,648 是 2^31 +5000。使用 32 位的位模式是 1000 0000 0000 0000 0001 0011 1000 1000
(其中第 32 位被设置)。在 32 位二进制补码整数表示中,这可能是一个很大的负数,-2,478,648。1
这显然不是程序员的本意。因此,标准要求超出 int 值范围的文字将具有 long int
或 long long int
类型,以先适合的为准,即使文字没有 l
或 {{1} } suffix! 2 如果两者的数字太大,则行为未定义,尽管实现可以自由提供和使用更大的整数类型。特别是,根据现代标准,十进制文字的类型永远不会是无符号的,除非以 ll
为后缀。
虽然我没有 C90 标准副本,而且旧草案似乎不可用,cppreference states 确实 C99 中的规则发生了变化,这可能是编译器警告的内容。值得注意的是,C99 引入了更大的整数类型 u
,并规定它至少可以表示数字 9223372036854775807。C90 中的文字类型规则显然是像 2147488648 这样的文字,其值在有符号 {{1} 之外}} 范围,但在 long long int
范围内是 long int
,以便尽可能地遵循程序员的意图。放弃这条规则的原因是无符号值在涉及有符号整数的表达式中可能会产生意想不到的后果:首先将所有值转换为无符号值。 unsigned long int
可以,但 unsigned long int
不行(-1 被转换为 unsigned int 4294967295)。编译器通常会警告混合符号表达式(不过 gcc 需要 -Wextra!)。演示:
2147488648u + 1
在您的情况下,您不必担心编译器警告。首先,您从不使用分配的值,因此编译器对其执行的任何操作都无关紧要;其次,将无符号整数值转换为可以保存该值的有符号整数类型是明确定义的并且按预期工作。尽管消除所有警告(并使用编译指示或编译器选项抑制不可避免的警告,以表明您已经意识到并考虑过它),但这是一种很好的做法。在您的情况下,您只需在文字后缀 if(2147488648u > -1)
以使其成为易于保存值的带符号 $ cat unsigned-mismatch.c && gcc -Wall -Wextra -pedantic -o unsigned-mismatch unsigned-mismatch.c && ./unsigned-mismatch
#include<stdio.h>
int main(void)
{
if(-1 < 2147488648u) { printf("-1 < 2147488648u is true\n"); }
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 true\n"); }
| ^
Oops: -1 < 2147488648u is false?
。 ll
也是 long long int
,所以它也可以保存值,所以一切都很好。
1 问题在于,在一个32位有符号整数中,使用二进制补码表示,这个位,即最高值位,用来表示值的符号。如果设置了该位,则该数字为负数。在二进制补码中 -1 是“所有位设置”,我们像往常一样从那里减去 1,直到我们到达 INT_MIN,除符号位外,所有位都消失了。
2 在 C++ 中,当重载解析意外地选择采用 a
参数的函数时,这可能再次令人惊讶,即使文字参数具有外观 em> 一个普通的 long long int
,没有任何后缀。