修复表达式中的整数溢出会导致意外行为 [关闭]
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
有效,但为什么a
和d
不有效:)
@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 中的标准定义。以上是关于修复表达式中的整数溢出会导致意外行为 [关闭]的主要内容,如果未能解决你的问题,请参考以下文章
iPhone 上 iOS Safari 中的过渡会导致意外行为