long long int n = 2000*2000*2000*2000; // overflow
long long int n = pow(2000,4); // works
long long int n = 16000000000000; // works
为什么第一个会溢出(将整数字面常量相乘后赋值给long long)?
与第二个或第三个有何不同之处?
long long int n = 2000*2000*2000*2000; // overflow
long long int n = pow(2000,4); // works
long long int n = 16000000000000; // works
为什么第一个会溢出(将整数字面常量相乘后赋值给long long)?
与第二个或第三个有何不同之处?
因为2000
是一个通常为32位的int
。只需使用2000LL
。
建议使用LL
后缀而不是ll
,这是由@AdrianMole在现已删除的评论中提出的,请查看他的答案。
默认情况下,整数字面量的类型是可以容纳其值但不小于int
的最小类型。2000
可以很容易地存储在int中,因为标准保证它实际上至少是一个16位类型。
算术运算符总是使用存在的类型中较大的类型,但不小于int
:
char*char
将被提升为operator*(int,int)->int
char*int
调用operator*(int,int)->int
long*int
调用operator*(long,long)->long
int*int
仍然调用operator*(int,int)->int
。关键是,该类型不取决于结果是否可以存储在推断的类型中。这正是发生在您的例子中的问题-乘法使用int
,但结果溢出仍然存储为int
。
C ++不支持像Haskell那样基于目标推断类型,因此赋值是无关紧要的。
你第一行代码右侧的常量(字面值)是 int
值(不是 long long int
)。因此,乘法使用 int
算术进行,会发生溢出。
要解决这个问题,请使用 LL
后缀将常量更改为 long long
:
long long int n = 2000LL * 2000LL * 2000LL * 2000LL;
事实上,正如Peter Cordes所述的评论中指出的那样,在第一个(最左边)或第二个常量上只实际上需要使用LL
后缀。这是因为当两种不同ranks类型相乘时,级别较低的操作数会被提升到较高级别的类型,如此处所述:Implicit type conversion rules in C++ operators。此外,由于*
(乘法)运算符具有left-to-right associativity,第一次乘法的“提升”结果将该提升传播到第二个和第三个乘法。
因此,以下任何一行也可在不溢出的情况下工作:
long long int n1 = 2000LL * 2000 * 2000 * 2000;
long long int n2 = 2000 * 2000LL * 2000 * 2000;
2000ll
)在C++中是有效的,且对于编译器来说完全没有歧义,但普遍共识是应该避免在long
和long long
整数字面量中使用小写字母“ell”,因为它很容易被人类读者误认为数字1
。因此,您会注意到在这里呈现的答案中使用了大写后缀2000LL
。*
从左往右结合,因此只有最左边的“2000LL”实际上需要一个“LL”后缀。随着其他两个*
运算符的计算,它们将被隐式提升为“long long”。在所有这些数字上都使用LL并没有什么不好的地方;对于阅读代码的人来说可以减少疑惑,但这只是作为今后参考的一点说明。C++运算符中的隐式类型转换规则 - Peter Cordesint
算术中相乘,但这没问题,因为2000*2000适合一个int
。 - Federico Polonilong long
算术运算,就使用 long long
操作数。 - Adrian Mole2000 * 2000
将溢出。我记得,C++标准允许使用16位的“int”,32位的“long”和64位的“long long”。 - Adrian Mole2000*2000*2000*2000
是4个int
值的乘积,其结果为一个int
值。当你将这个int
值赋给long long int n
时,溢出已经发生了(如果int
是32位,则结果值将无法容纳)。
你需要确保不会发生溢出,因此当你写下
long long int n = static_cast<long long int>(2000)*2000*2000*2000;
请确保进行 long long int
乘法运算(long long int
与 int
相乘会返回一个 long long int
, 因此在您的情况下不会发生溢出)。
一种更简洁且更好的方法是,使用 2000LL
或 2000ll
替代 static_cast
。这样可以将整数字面值指定为正确的类型。对于适合于 int
的 2000,不需要这样做,但如果有超出 int
范围的更高值,则需要这样做。
long long int n = 2000LL*2000*2000*2000;
long long int n = 2000LL*2000LL*2000LL*2000LL;
static_cast<long long int>(2000)
来避免这个问题(尽管我通常会省略隐含的“int”部分)。但在这种情况下,使用2000LL
更为简单。 - ShadowRanger/Wall
的 Visual Studio 中的 clang-cl,并且确实会出现警告。另外,为什么要使用可以做任何事情的 C 风格转换,而更柔和的 static_cast
就足够了呢? - Adrian Molelong long int n = 2000*2000*2000*2000;
被解释为下列内容:
long long int n = ((2000*2000)*2000)*2000;
以下是步骤(假设为32位int
):
(2000*2000)
是两个int
值的乘积,得到4000000,另一个int
值。((2000*2000)*2000)
是上述产生的int
值4000000与一个int
值2000相乘。如果该值可以适合int
中,则会得到8000000000。但是我们假设的32位int只能存储最大值231-1 = 2147483647。因此我们在这一点上就会发生溢出。int
的赋值将会发生(如果没有溢出),将其赋给long long
变量,它将保留该值。由于我们确实发生了溢出,所以该语句具有未定义行为,因此无法保证步骤3和4。
与第二个或第三个有什么不同?
long long int n = pow(2000,4);
pow(2000,4)
将2000
和4
转换为double
(请参阅some docs on pow
),然后函数实现尽最大努力产生结果的良好近似值,作为double
。然后赋值将此double
值转换为long long
。
long long int n = 16000000000000;
字面量16000000000000
太大,无法适应int
,因此其类型是可以容纳该值的下一个有符号类型。它可能是long
或long long
,具体取决于平台。有关详细信息,请参见Integer literal#The type of the literal。然后赋值将此值转换为long long
(如果字面量的类型已经是long long
,则只需写入即可)。
首先是使用整数(通常为32位)进行乘法运算,因为这些整数无法存储2000^4
,所以会发生溢出。然后将结果转换为long long int
。
其次是调用pow函数,该函数将第一个参数转换为double
并返回一个double
。然后将结果转换为long long int
。在这种情况下不存在溢出,因为计算是在double值上进行的。
int
可以非常狭窄,最窄为16位,在一些现代的嵌入式微控制器上(例如AVR或MSP430)会出现,因此如果最终值大于32767,则需要考虑可移植性。 (您不太可能找到具有64位“int”的C实现,尽管我记得有极少数情况。并且从历史上看,“int”可能不完全是32位。)难以精确地进行描述而不使答案膨胀,但是可以说“使用int
(通常为32位)”。 - Peter Cordes如果您想理解这个问题,可以尝试使用以下C++代码:
#include<iostream>
#include<cxxabi.h>
using namespace std;
using namespace abi;
int main () {
int status;
cout << __cxa_demangle(typeid(2000*2000*2000*2000).name(),0,0,&status);
}
如您所见,类型为int
。
在C语言中,您可以使用(由此提供):
#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
#define typename(x) _Generic((x), /* Get the name of a type */ \
\
_Bool: "_Bool", unsigned char: "unsigned char", \
char: "char", signed char: "signed char", \
short int: "short int", unsigned short int: "unsigned short int", \
int: "int", unsigned int: "unsigned int", \
long int: "long int", unsigned long int: "unsigned long int", \
long long int: "long long int", unsigned long long int: "unsigned long long int", \
float: "float", double: "double", \
long double: "long double", char *: "pointer to char", \
void *: "pointer to void", int *: "pointer to int", \
char(*)[]: "pointer to char array", default: "other")
unsigned int a = 3;
int main() {
printf("%s", typename(a-10));
return 0;
}
C
整型字面量的最小默认类型是int
,但仅当字面量超过此限制时,它的类型才变为unsigned int
; 如果大于此限制,则将其赋予long int
类型,因此2000都是int
。但是,在使用一元或二元运算符对表达式执行操作时,使用隐式类型层次结构来决定类型,而不是结果的值(与字面量本身不同,后者在确定类型时使用字面量的长度),这是因为C使用类型强制转换而不是类型合成。为了解决这个问题,您需要在2000上使用长后缀ul
来显式指定字面量的类型。double
,但是可以通过f
后缀进行更改。前缀不会改变十进制或整数字面量的类型。char []
,虽然实际上它是一个const char []
,只是指向在.rodata
中表示该字符串字面值的第一个字符的地址,并且可以像使用任何数组一样使用一元符号&"string"
获取地址,这个地址与"string"
相同(即地址相同),只是类型不同(char (*)[7]
与char[7]
;"string"
即char[]
在编译器级别上不仅仅是指向数组的指针,它本身就是数组,而一元符号提取的只是指向数组的指针)。前缀u
将其更改为char16_t
数组,它是一个unsigned short int
;前缀U
将其更改为char32_t
数组,它是一个unsigned int
;前缀L
将其更改为wchar_t
数组,它是一个int
。u8
是一个char
,未带前缀的字符串使用实现特定的编码,通常与u8
相同,即UTF-8,其中ASCII是一个子集。只有对字符串字面值可用的原始(R
)前缀(仅在GNU C上可用(std=gnu99
以后))可以添加前缀,即uR
或u8R
,但这不会影响类型。int
,除非带有前缀u
(u'a'
为unsigned short int
)或U
(U'a'
为unsigned int
)。在字符字面值上使用u8
和L
时,都是int
。字符串或字符字面值中的转义序列不影响编码和类型,只是将要编码的字符实际呈现给编译器的一种方式。10i+1
或10j+1
的类型为complex int
,其中实部和虚部都可以有后缀,如10Li+1
,在这种情况下,虚部变成了长整型,整个类型为complex long int
,升级了实部和虚部的类型,因此无论后缀放在哪里或者是否放在两个位置上,都不会产生影响。如果不匹配,则总是使用两个后缀中最大的作为整体类型。signed
进行符号扩展;对于unsigned
进行零扩展 - 这基于要转换的文字或表达式的类型,而不是被转换成的类型,因此signed int
扩展为unsigned long int
),将文字截断/扩展为该类型的表达式,而不是文字固有地具有该类型。
再次提醒,最小的默认类型是 int
,对于最小的文字基础。文字基础即文字的实际值和后缀会根据下表影响最终的文字类型,在每个后缀的框中,根据实际文字基础的大小,列出了最终类型的顺序,从小到大排列。对于每个后缀,文字的最终类型只能等于或大于后缀类型,并且基于文字基础的大小。C 表现出相同的行为。当大于 long long int
时,根据编译器的不同,会使用 __int128
。我认为您还可以创建自己的文字后缀运算符 i128
并返回该类型的值。char []
。 &"string"
的类型是const char (*) [7]
,+"string"
的类型是const char *
(在C中,只能使用"string"+0
进行衰减)。 C ++不同之处在于后两种形式获得了const
,但在C中它们没有。字符串前缀的行为与C相同。
pow(2000,4)
使用的是double
,而2000*2000*2000*2000
使用的是int
。 - Jarod42int
。2000 是一个 int,不是 long long int。 - drescherjm2^31 - 1
,即2,147,483,647
,小于200020002000*2000,并且所有的2000都是整型,所以计算结果会被视为整型而不是长整型。 - drescherjm