【问题标题】:Why does long long n = 2000*2000*2000*2000; overflow?为什么long long n = 2000*2000*2000*2000;溢出?
【发布时间】:2021-05-27 00:01:03
【问题描述】:
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)?

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

【问题讨论】:

  • pow(2000,4) 使用 .. double, 2000*2000*2000*2000 使用 int
  • 第一个是使用int计算的。 2000 是一个整数。不长长整型
  • 因为最大 32 位 int 值是 2^31 − 1,即 2,147,483,647 小于 2000* 2000* 2000*2000 并且由于所有 2000 都是 int,所以计算是作为 int 完成的。不像 long long int
  • 定期提醒:您对操作结果所做的操作不会影响该结果的计算方式。
  • TL:DR: 这似乎是当前最好的规范问答我发现整数文字的表达式溢出,所以我重复锤击或编辑了重复列表其他人指向这个。

标签: c++ math types literals integer-overflow


【解决方案1】:

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;

您确保您正在执行 long long int 乘法(long long intint 相乘返回 long long int,因此在您的情况下不会溢出)。

更短(更好的方法)是写2000LL2000ll 而不是static_cast。这为整数文字提供了正确的类型。这对于适合 int 的 2000 不需要,但对于不适合 int 的更高值则需要。

long long int n = 2000LL*2000*2000*2000;
long long int n = 2000LL*2000LL*2000LL*2000LL;

【讨论】:

  • Clang 说:警告:使用旧式演员表 [-Wold-style-cast]
  • @AdrianMole:大概你可以使用 C++ 风格的转换,static_cast&lt;long long int&gt;(2000) 来避免这个问题(尽管我通常会放弃隐含的int 部分)。也就是说,2000LL 在这种情况下要简单得多。
  • @AdrianMole -Wold-style-cast 不包含在 -Wall -Wextra 中。我认为 C 风格强制转换为非指针、非引用类型没有任何害处。
  • @HolyBlackCat 我使用 clang-cl via Visual Studio(使用 /Wall)并且 确实 发出警告。另外,当更柔和的static_cast 就足够时,为什么还要使用 do-anything C 风格的演员表?
  • re C 风格转换无害 -- 阅读源代码时,任何 C 风格转换都是自动代码审查问题。因此,每次再次查看它时,以这种方式放置它会浪费时间和注意力。函数样式是相同的字符数。
【解决方案2】:

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

@AdrianMole 在评论中建议使用LL 后缀而不是ll,现已删除。请查看他的answer

默认情况下,整数字面量是可以保持其值但不小于int 的最小类型。 2000 可以很容易地存储在 int 中,因为标准保证它实际上至少是 16 位类型。

算术运算符总是以存在的类型中较大的类型调用,但不小于int

  • char*char 将被提升为 operator*(int,int)-&gt;int
  • char*int 致电operator*(int,int)-&gt;int
  • long*int 致电operator*(long,long)-&gt;long
  • int*int 仍然调用operator*(int,int)-&gt;int

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

C++ 不支持像 Haskell 那样根据目的地推断类型,因此分配无关紧要。

【讨论】:

【解决方案3】:

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

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

【讨论】:

  • int 可以窄至 16 位,并且在一些现代嵌入式微控制器(如 AVR 或 MSP430)上,因此如果最终值 > 32767,您需要担心可移植性.(您不太可能找到具有 64 位 int 的 C 实现,尽管 IIRC 很少。而且从历史上看,int 可能不完全是 32。)如果没有臃肿的答案很难准确,但您可以说“使用 int(通常为 32 位)”
【解决方案4】:

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

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

long long int n = 2000LL * 2000LL * 2000LL * 2000LL;

cppreference

事实上,正如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++,并且对编译器完全明确,但有一个 general consensus 表示小写字母, 'ell' 应避免在 longlong long 整数文字中使用,因为它很容易被人类读者误认为是数字 1。因此,您会注意到此处提供的所有答案都使用了2000LL(大写后缀)。

【讨论】:

  • * 从左到右分组,所以只有最左边的 2000LL 实际上需要 LL 后缀。随着对其他​​ 2 个 * 运算符的评估继续进行,其他人都将被隐式提升为 long long。在所有这些上使用 LL 肯定不是一件坏事。人类在阅读代码时不必担心,而只是为了将来参考。 Implicit type conversion rules in C++ operators
  • @PeterCordes 我已将您的评论纳入我的回答 - 希望您不介意!起初我有点犹豫,因为这个问题(部分)在其他答案(特别是 Werner 的)中得到了处理。不过,希望我已经更详细地解释了这个问题。
  • 人们找到改进基于 cmets 的帖子的方法总是一件好事,包括借用一些措辞,尤其是像这样的规范问答,希望许多未来的读者最终会看到。改进帖子正是 cmets 的目的,所以欢呼。 :) 是的,我在这里发表评论后才注意到维尔纳的回答。解释这一点绝对是好的;在寻找重复项时(我最终将其作为重复项关闭,因为它有很好的答案),我发现一些错误地指出使 any 的数字 LL 有效。
  • 如果 LL 在第三个常数上,那不也可以吗?前两个乘以 int 算术,但这很好,因为 2000*2000 适合 int
  • @FedericoPoloni 另请注意(也许更重要的是)如果int 是 16 位宽,2000 * 2000 溢出。 IIRC,C++ 标准允许 16 位 int、32 位 long 和 64 位 long long
【解决方案5】:

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

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

表达式

long long int n = 2000*2000*2000*2000;

评估如下:

long long int n = ((2000*2000)*2000)*2000;

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

  1. (2000*2000) 是两个 int 值的乘积,产生 4000000,另一个 int 值。
  2. ((2000*2000)*2000) 是上面产生的 int 值 4000000 与 int 值 2000 的乘积。如果该值适合 int,这将产生 8000000000。但是我们假设的 32 位 int 可以存储最大值 231-1=2147483647。所以我们此时会溢出。
  3. 如果上面没有溢出,就会发生下一次乘法。
  4. 将生成的 int 乘积赋值(如果不是溢出)到 long long 变量,这将保留该值。

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

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

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

pow(2000,4)20004 转换为double(参见some docs on pow),然后函数实现会尽力生成一个很好的近似结果,如double。然后赋值将这个double 值转换为long long

  • long long int n = 16000000000000;

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

【讨论】:

  • 感谢您的详细解答!
【解决方案6】:

您可能想在 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 中,您可以使用 (courtesy of):

#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;
}

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

C

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

同样,十进制字面量的默认类型是double,但可以使用f 后缀来更改。前缀不会改变十进制或整数文字的类型。

字符串字面量的类型是char [],虽然它实际上是const char [],并且只是该字符串字面量在.rodata中的实际表示中的第一个字符的地址,地址可以是像使用一元 & 符号&amp;"string" 的任何数组一样,它与"string" 具有相同的值(地址),只是类型不同(char (*)[7]char[7]"string"char[] 不只是(在编译器级别)指向数组的指针,它数组,而一元&符号仅提取指向数组的指针)。 u 前缀将其更改为char16_t 的数组,即unsigned short intU 前缀将其更改为char32_t 的数组,即unsigned int;并且L 前缀将其更改为wchar_t 的数组,即intu8char 并且无前缀字符串使用特定于实现的编码,这通常与 u8 相同,即 UTF-8,其中 ASCII 是一个子集。 raw (R) prefix 仅适用于字符串文字(并且仅适用于 GNU C(std=gnu99 以上))可以作为前缀,即 uRu8R,但这不会影响类型。

字符文字的类型是int,除非前缀为uu'a'unsigned short int)或UU'a'unsigned int)。 u8L 在用于字符文字时都是 int。字符串或字符文字中的转义序列不会影响编码和类型,它只是将要编码的字符实际呈现给编译器的一种方式。

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

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

C++

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

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

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

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

【讨论】:

  • @MaksimKuzmin 这个问题看起来很简单,但它隐藏了 CPU 级别的数字的底层系统表示以及 C/C++ 语言如何处理它。事实上,这不是一个那么简单的问题,所以这个非常详尽的答案对于这个问题是有意义和有用的。
  • @Zilog80 是的,我只是想要一个关于文字的指南来参考
  • @LewisKelsey 你的意思是把链接放到文字文档而不是嵌入它会更好,我完全同意。我主要指出这样一个事实,即 CPU 级别的数字表示值得一些详细说明[但实际上不是嵌入式文档摘录]。
猜你喜欢
  • 2013-02-06
  • 2011-02-15
  • 1970-01-01
  • 2023-01-20
  • 1970-01-01
  • 2023-03-06
  • 2014-05-03
  • 2010-09-26
相关资源
最近更新 更多