修复表达式中的整数溢出会导致意外行为 [关闭]

Posted

技术标签:

【中文标题】修复表达式中的整数溢出会导致意外行为 [关闭]【英文标题】:Fixing integer overflow in expression leads to an unexpected behavior [closed] 【发布时间】:2021-03-03 17:29:35 【问题描述】:

请考虑以下代码:

#include <iostream>
#include <limits>

int main() 
    size_t a = 1024ull * 1024ull * 1024ull * 2ull;
    std::cout << "1024ull * 1024ull * 1024ull * 2ull = " << a << std::endl;

    size_t b = 1024 * 1024 * 1024 * 2;
    std::cout << "1024 * 1024 * 1024 * 2 = " << b << std::endl;

    size_t c = 18446744071562067968u;
    std::cout << "18446744071562067968u = " << c << std::endl;

    size_t d = 1ULL << 31;
    std::cout << "1ULL << 31 = " << d << std::endl;

    size_t e = 1 << 31;
    std::cout << "1 << 31 = " << e << std::endl;

    std::cout << "max size_t = " << std::numeric_limits<size_t>::max()
              << std::endl;

当我使用 GCC 或 Clang 构建它时,我看到以下警告:

$ g++ -std=c++17 ./main.cpp
./main.cpp: In function ‘int main()’:
./main.cpp:8:35: warning: integer overflow in expression [-Woverflow]
     size_t b = 1024 * 1024 * 1024 * 2;
                ~~~~~~~~~~~~~~~~~~~^~~

但是,当我运行代码时,只有“错误”的版本会产生正确的结果:

1024ull * 1024ull * 1024ull * 2ull = 2147483648
1024 * 1024 * 1024 * 2 = 18446744071562067968
18446744071562067968u = 18446744071562067968
1ULL << 31 = 2147483648
1 << 31 = 18446744071562067968
max size_t = 18446744073709551615

GCC 版本:

gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Clang 版本:

clang version 6.0.0-1ubuntu2 (tags/RELEASE_600/final)
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin

对这种行为有什么合理的解释吗?

关闭:2147483648 是正确的结果。

【问题讨论】:

我不确定b 是否可以工作,因为是的,这会溢出int 你的意思是哪一个是错误的版本和正确的结果? 1024 * 1024 * 1024 * 2 = 18446744071562067968 对我来说看起来不正确,这是编译器警告的。 我不知道为什么b 有效,但为什么ad 不有效:) @fsquirrel,在哪方面工作? 1024 * 1024 * 1024 * 2 不应该是 2147483648 吗? @ilkkachu,你说得对,我会关闭这个。 【参考方案1】:

表达式

1024 * 1024 * 1024 * 2

(代数为2147483648)由int 类型组成。这将溢出 32 位 int,并且这样做的行为是 undefined。请注意,将 2147483648 直接分配给 32 位 int实现定义直到并包括 C++17 并从 C++20 定义,但您还没有这样做。

观察到的行为与在 32 位 2 的补码平台上等于 INT_MIN 的表达式一致,并且转换为 64 位 std::size_t,但不要依赖该行为.

【讨论】:

FWIW,C++20 已经出,如果 OP 正在使用它,那么它不再是 UB。 @NathanOliver:非常好的观点。 C++20 在这方面是一个巨大的飞跃。我将在短期内将我的代码库迁移到它。 我很高兴他们刚刚决定强制执行这种行为。它会降低灵活性,但在我看来,“安全”收益是一个胜利。 @NathanOliver:尽管我喜欢在这个网站上给人的印象,但自从大学以来,我没有使用过 1 的补码机器或非 ASCII 编码的机器。而且这样的机器仍然有 C++11。 (C++14 要求 2 的补码 signed char。) @Bathsheba 是的。这只是您在答案中描述为实现定义的东西,它已由 C++20 中的标准定义。

以上是关于修复表达式中的整数溢出会导致意外行为 [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

增量算子在字符类型边界上的行为

进入我的循环时的Python代码意外行为[关闭]

iPhone 上 iOS Safari 中的过渡会导致意外行为

迭代器当中的死循环导致内存溢出

意外的类型名称“BOOL”:iOS SDK 6.1(设备)中的预期表达式

以下链式赋值是不是会导致未定义的行为?