为什么long long n = 2000*2000*2000*2000;溢出?

Fab*_*llo 149 c++ math types integer-overflow literals

long long int n = 2000*2000*2000*2000;    // overflow

long long int n = pow(2000,4);            // works
long long int n = 16000000000000;         // works
Run Code Online (Sandbox Code Playgroud)

为什么第一个溢出(乘以整数文字常量以分配给 long long)?

它与第二个或第三个有什么不同?

Qui*_*mby 140

因为2000是一种int通常是32位的。只需使用2000LL.

@AdrianMole 在评论中建议使用LL后缀代替ll,现在已删除。请检查他的回答

默认情况下,整数文字是可以保存其值但不小于 的最小类型int2000可以很容易地存储在 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

类型不依赖于结果是否可以存储在推断类型中。这正是您的情况发生的问题 - 乘法是用ints完成的,但结果溢出,因为它仍然存储为int.

C++ 不支持像 Haskell 那样基于目的地推断类型,因此赋值无关紧要。


Adr*_*ica 93

第一行代码的 RHS 上的常量(文字)是int值(不是 long long int)。因此,乘法是使用int算术进行的,这会溢出。

要解决此问题,请long long使用LL后缀创建常量:

long long int n = 2000LL * 2000LL * 2000LL * 2000LL;
Run Code Online (Sandbox Code Playgroud)

cppreference

事实上,如在由注释指出彼得科尔德,该LL后缀是实际上只需要在所述第一(最左边)或第二恒定。这是因为,当两个不同等级的类型相乘时,较低等级的操作数会提升为较高等级的类型,如下所述:C++ 运算符中的隐式类型转换规则。此外,由于*(乘法)运算符具有从左到右的关联性,第一次乘法的“提升”结果将该提升传播到第二次和第三次。

因此,以下任一行也不会溢出:

long long int n1 = 2000LL * 2000 * 2000 * 2000;
long long int n2 = 2000 * 2000LL * 2000 * 2000;
Run Code Online (Sandbox Code Playgroud)

注意:虽然小写后缀(如2000ll)是有效的C ++和完全明确的编译器,有一个一般的共识是,小写字母“ELL”,应在避免longlong long整数常量,因为它可以很容易被误认为,由人类读者,对于数字,1。因此,您会注意到2000LL(大写后缀)已在此处提供的所有答案中使用。

  • `*` 从左到右分组,因此只有最左边的 `2000LL` 实际上需要 LL 后缀。随着其他 2 个“*”运算符的评估进行,其他运算符都将隐式提升为“long long”。对所有这些都使用 LL 当然不是一件坏事;人们在阅读代码时不必担心,但仅供将来参考。[C++ 运算符中的隐式类型转换规则](/sf/ask/389410031/) (45认同)
  • 当人们找到基于评论改进帖子的方法时,包括借用一些措辞,尤其是像这样的规范问答,希望许多未来的读者最终会看到,这总是一件好事。改进帖子正是评论的目的,所以干杯。:) 是的,我在这里发表评论后才注意到维尔纳的回答。解释这一点肯定是好的;在寻找重复项时(我最终将其作为重复项关闭,因为它有很好的答案),我发现一些错误地表明使“任何”数字 LL 有效。 (3认同)
  • @FedericoPoloni 还要注意(也许更重要的是)如果 `int` 是 16 位宽,`2000 * 2000` **将会**溢出。IIRC,C++ 标准允许 16 位“int”、32 位“long”和 64 位“long long”。 (2认同)

Wer*_*nze 49

2000*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;
Run Code Online (Sandbox Code Playgroud)

你确保你正在做long long int乘法(long long int乘以int返回 a long long int,所以在你的情况下没有溢出)。

更短(更好)的方法是编写2000LL2000ll代替static_cast. 这为整数文字提供了正确的类型。这对于适合 2000intint.

long long int n = 2000LL*2000*2000*2000;
long long int n = 2000LL*2000LL*2000LL*2000LL;
Run Code Online (Sandbox Code Playgroud)

  • Clang 说:*警告:使用旧式演员 [-Wold-style-cast]*! (5认同)
  • @AdrianMole:大概你可以使用 C++ 风格的转换“static_cast&lt;long long int&gt;(2000)”来避免这个问题(尽管我通常会删除隐含的“int”部分)。也就是说,在这种情况下,“2000LL”要简单得多。 (5认同)

Rus*_*lan 29

其他答案(截至撰写本文时)似乎不够明确,无法回答所述问题。我会努力填补这个空白。

为什么第一个溢出(乘以整数文字常量以分配给 long long)?

表达方式

long long int n = 2000*2000*2000*2000;
Run Code Online (Sandbox Code Playgroud)

评估如下:

long long int n = ((2000*2000)*2000)*2000;
Run Code Online (Sandbox Code Playgroud)

步骤在哪里(假设 32 位int):

  1. (2000*2000)是两个int值的乘积,产生 4000000,另一个int值。
  2. ((2000*2000)*2000)是上面产生的int值 4000000 与int值 2000的乘积。如果该值可以放入int. 但是我们假设的 32 位 int 可以存储最大值 2 31 -1=2147483647。所以我们在这一点上就溢出了。
  3. 如果上面没有溢出,就会发生下一个乘法。
  4. 结果int乘积的分配将发生(如果不是溢出)到long long变量,这将保留该值。

由于我们确实有溢出,该语句具有未定义的行为,因此不能保证第 3 步和第 4 步。

它与第二个或第三个有什么不同?

  • long long int n = pow(2000,4);

pow(2000,4)转换20004double(见一些文档pow),然后函数实现会尽全力产生结果的一个很好的近似,作为一个double。然后赋值将此double值转换为long long.

  • long long int n = 16000000000000;

文字16000000000000太大而无法放入int,因此它的类型是下一个可以适合该值的有符号类型。它可能是longlong long,具体取决于平台。有关详细信息,请参阅整数文字#文字的类型。然后赋值将此值转换为long long(或仅写入它,如果文字的类型long long已经存在)。


Dea*_*son 19

第一个是使用整数(通常为 32 位)的乘法。它溢出是因为这些整数不能存储2000^4。然后将结果强制转换为long long int

第二个调用 pow 函数,该函数将第一个参数强制转换为double并返回 a double。然后将结果强制转换为long long int。在这种情况下没有溢出,因为数学是在 double 值上完成的。

  • `int` 可以窄至 16 位,并且在一些现代嵌入式微控制器(如 AVR 或 MSP430)上,因此如果最终值 &gt; 32767,您需要担心这一点。(您不太可能找到具有 64 位 `int` 的 C 实现,尽管 IIRC 很少见。而且历史上 int 可能不完全是 32。)在不膨胀答案的情况下很难精确,但你可以说“使用 `int`(通常是 32) -少量)” (9认同)

Lew*_*sey 5

您可能希望在 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);
}
Run Code Online (Sandbox Code Playgroud)

如您所见,类型为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;
}
Run Code Online (Sandbox Code Playgroud)

这里表达式unsigned int的类型是因为类型不匹配隐式将类型升级为unsigned int和之间的最大类型int,即unsigned int。的unsigned int下溢到一个大的正,当分配给或解释为这将是预期的负int。计算结果将始终unsigned int与所涉及的值无关。

C

没有后缀的整数文字的最小默认类型是int,但只有当文字超过此值时,其类型才会变为unsigned int; 如果大于它,则给出 a 的类型long int,因此 2000s 都是ints。然而,在文字上执行的表达式的类型,使用一元或二元运算符,使用隐式类型层次结构来决定类型,而不是结果的值(不像文字本身,它使用文字的长度来决定类型) . 为了解决这个问题,您必须ul在 2000 年代使用长后缀来明确指定文字的类型。

同样,十进制文字的默认类型是double,但这可以通过f后缀更改。前缀不会改变十进制或整数文字的类型。

字符串文字的类型是char [],尽管它实际上是const char [], 并且只是该字符串文字 in 的实际表示中第一个字符.rodata的地址,并且可以像使用一元与号的任何数组一样获取该地址&"string",这是与 相同的值(地址)"string",只是不同的类型(char (*)[7]vs. char[7];"string"char[]不仅仅是(在编译器级别)指向数组的指针,它数组,而一元&符号仅提取指向数组的指针)。的u前缀改变这对的阵列char16_t,这是一种unsigned short int; 的U前缀它改变到的阵列char32_t,这是一种unsigned int; 并且L前缀将其更改为一个数组wchar_t这是一个int. u8is achar并且一个不带前缀的字符串使用实现特定的编码,它通常与u8即 UTF-8相同,其中 ASCII 是一个子集。仅可用于字符串文字的原始 ( R) 前缀(并且仅在 GNU C(std=gnu99以后)上可用)可以作为前缀 ieuRu8R,但这不会影响类型。

字符文字的类型是int除非以u( u'a'is unsigned short int) 或U( U'a'is unsigned int)为前缀。u8和 和Lint用于字符文字。字符串或字符文字中的转义序列不会影响编码,因此不会影响类型,它只是将要编码的字符实际呈现给编译器的一种方式。

复杂文字10i+1or 10j+1is的类型complex int,其中实部和虚部都可以有一个后缀,例如10Li+1,在这种情况下,这使虚部变长,整体类型为complex long int,并升级了实部和虚部的类型部分,所以你把后缀放在哪里或者你是否把它放在两者上都没有关系。不匹配将始终使用两个后缀中最大的作为整体类型。

使用显式转换而不是文字后缀总是会导致正确的行为,如果您正确使用它并且知道它截断/扩展的语义差异(符号扩展为signed;零扩展为unsigned– 这基于文字的类型或表达式被强制转换而不是被强制转换的类型,因此 asigned int被符号扩展为unsigned long int) 一个文字到该类型的表达式,而不是文字本身具有该类型。

C++

在此处输入图片说明

同样,最小默认类型是int最小文字基数的an 。文字基础即文字的实际值,后缀根据下表影响最终文字类型,其中在每个后缀的每个框中,最终类型的顺序根据实际的大小从最小到最大列出字面基础。对于每个后缀,文字的最终类型只能等于或大于后缀类型,并基于文字库的大小。C 表现出相同的行为。当大于 a 时long long int,取决于编译器,__int128使用。我认为您还可以创建自己的文字后缀运算符i128并返回该类型的值。

十进制文字的默认类型与 C 相同。

字符串文字的类型是char []. 的类型的&"string"const char (*) [7]和类型+"string"IS const char *(在C只能衰减使用"string"+0)。C++ 的不同之处在于后两种形式获取 aconst但在 C 中它们没有。字符串前缀的行为与 C 中相同

字符和复杂文字的行为与 C 相同。


归档时间:

查看次数:

10915 次

最近记录:

4 年,5 月 前