UB / compiler bug / wrong program / something else?
这篇文章用于澄清大家对一些 c++ 语法/标准库问题的误区,以正视听。
- 有符号整形算术溢出 是 UB。
- 但是,无符号整数整形溢出 不是 UB。
- 左/右移负数或大于等于数据类型宽度的值 也是 UB。(如:
uint64_t左移 $64$ 位)c++20 标准规定了负数进行左移右移所得结果。此前,负数左移未定义,负数右移由实现定义。详见 cppreference。 整数转换中,如果目标类型有符号,那么当源整数能以目标类型表示时不会更改它的值。否则结果
- 由实现定义 (C++20 前)
- 等于源值模 $2^n$ 的唯一目标类型值,其中 $n$ 用于表示目标类型的位数 (C++20 起)
这与未定义的有符号算术溢出不同,并意味着
int32_t(int64_t(INT64_MAX))一定会得到-1。
- 带文件操作的程序正常运行结束,不需要
fclose。
- 手动
fclose很有可能带来更多问题,尤其是当cin关闭同步流的时候。 - c++ 流在执行析构函数的时候会刷新缓冲区并自动关闭文件。
- c 输出函数会在
main结束后完成对缓冲区的刷新。只要程序正常终止,缓冲区内的所有内容都会被写入文件,无需手动fclose。
- 关同步流最简单的方式是
cin.tie(0)->sync_with_stdio(0)。
cout.tie(0)是完全没有必要的,因为cout默认就没有tie任何东西。- 没有任何证据表明在
freopen前关闭流同步会造成任何问题。
- 请慎用
__int128。
- STL 对
__int128相关特化(如std::abs<__int128>,std::hash<__int128>,std::is_integral_v<__int128>)均在 gnu++ 标准而非 c++ 标准中,故考试编译选项中无法使用。 - 数组下标访问使用
__int128可能造成编译器 bug(于 14.1 修复,影响 gcc9.3),请务必留意。一份复现样例在 这里(这是缩减后的代码,gcc 12 中不会出问题,但事实上原始代码在 12 也会爆)。
- 需要慎用
size_t。由于其是无符号整数类型,将size_t(0)减去 $1$ 会得到UINT64_MAX,而且-1 < size_t(0)会得到 $0$。由于其是 STL 容器size()函数的返回值类型,请务必注意这一点。 - 结构化绑定在 c++17 才进入标准,但是在正式比赛中可以使用(只会有 warning)。此外,
std::array也是可以通过结构化绑定拆开的,这使得其成为std::tuple在元素类型相同时的上位替代。
std::array<int, 4> qwq = {1, 2, 3, 4};
auto [a, b, c, d] = qwq;
std::cout << a << ' ' << b << ' ' << c << ' ' << d << '\n'; // 输出:1 2 3 4- 如果你写的一个成员函数要区分在常量/非常量语境下的返回值,可以这样写:
struct QwQ {
int a[5][5];
const int *operator[](int _id) const & { return a[_id]; } // (1)
int *operator[](int _id) & { return a[_id]; } // (2)
int operator*(const QwQ &rhs) const { return (*this)[0][0] * rhs[1][1]; } // 调用 (2)
};std::unordered_map的清空复杂度关于桶数而非元素数线性,请务必注意。
- 例如,你应该通过
decltype(x){}.swap(x)或者x.erase(x.begin(), x.end())清空x,而非x.clear()。 - 值得注意的是,
__gnu_pbds::gp_hash_table在效率方面优于std::unordered_map,可以考虑自己写一个不容易被卡的哈希函数(例如:__builtin_bswap64(x*C),$C$ 是大奇数)并使用gp_hash_table。
- 在考场上,如何判断是你写挂了还是踩到编译器 bug 了?
- 开
-Wall -Wextra -Wshadow -Wconversion -fsanitize=address,undefined。如果报错,大概率是你写挂了。 - 检查是否在关同步流后仍然使用了
cstdio的函数,或者不当使用了fclose。 - 关闭
-O2。如果通过了上一条测试,并且关闭-O2后得到了正确的输出,那么有 一定概率 是真的遇到编译器 bug 了。 - 目前,笔者只听说有选手由于
__int128下标访问触发过编译器 bug,其余问题在 OI 中触发概率极低,一般可忽略。