上溢/下溢是执行时未定义的行为吗
Is over/underflow an undefined behavior at execution time?
我读到了关于未定义行为的文章,我不确定这是一个仅限编译时的功能,还是可以在执行时发生。
我很理解这个例子(摘自维基百科的Undefined Behavior页面):
C语言的一个例子:
int foo(unsigned x) { int value = 5; value += x; if (value < 5) bar(); return value; }
x
的值不能为负数,并且考虑到有符号整数溢出在C中是未定义的行为,编译器可以假设在if检查value >= 5
的行。因此,if
和对函数bar
的调用可以被编译器忽略,因为if
没有副作用,并且它的条件永远不会得到满足。因此,上面的代码在语义上等价于:int foo(unsigned x) { int value = 5; value += x; return value; }
但这发生在编译时。
如果我写,例如:
void foo(int x) {
if (x + 150 < 5)
bar();
}
int main() {
int x;
std::cin >> x;
foo(x);
}
然后用户键入MAX_INT - 100
("2147483547">,如果是32位整数)。
会有一个整数溢出,但AFAIK,是CPU的算术逻辑单元会产生溢出,所以这里不涉及编译器。
它仍然是未定义的行为吗?
如果是,编译器如何检测溢出?
我能想到的最好的是CPU的溢出标志。如果是这种情况,这是否意味着如果在执行时随时设置CPU的溢出标志,编译器可以做任何他想做的事情?
是的,但不一定是我认为你的意思,也就是说,如果在机器代码中有一个加法,并且在运行时该加法包装(或者以其他方式溢出,但在大多数架构中它会包装),而不是UB本身。UB仅在C(或C++)的域中。这种添加可能是添加无符号整数,或者是编译器可以进行的某种优化,因为它知道目标平台的语义,并且可以安全地使用依赖于包装的优化(但您不能,当然,除非您使用无符号类型)。
当然,这并不意味着使用"仅在运行时包装"的构造是安全的,因为这些代码路径在编译时也会受到影响。例如,在您的示例中,
extern void bar(void);
void foo(int x) {
if (x + 150 < 5)
bar();
}
由GCC 6.3编译,目标是x64到
foo:
cmp edi, -145
jl .L4
ret
.L4:
jmp bar
这相当于
void foo(int x) {
if (x < -145)
bar(); // with tail call optimization
}
如果您假设有符号整数溢出是不可能的(从某种意义上说,它对输入设置了一个隐含的前提条件,使溢出不会发生),这也是一样的。
您对第一个示例的分析不正确。value += x;
相当于:
value = value + x;
在这种情况下,value
是int
,x
是unsigned
,所以通常的算术转换意味着value
首先被转换为无符号,所以我们有一个无符号加法,根据定义它不能溢出(它根据模块算术具有定义明确的语义)。
当无符号结果被分配回value
时,如果它大于INT_MAX
,则这是一个超出范围的分配,具有实现定义的行为。这不是溢出,因为它是赋值,而不是算术运算。
因此,哪些优化是可能的取决于实现如何定义整数的超范围赋值行为。现代系统都采用具有相同2的补码表示的值,但历史上其他系统做了一些不同的事情。
因此,原始示例在任何情况下都没有未定义的行为,并且对于大多数系统来说,建议的优化是不可能的。
第二个例子与第一个例子无关,因为它不涉及任何无符号算术。如果x > INT_MAX - 150
,则表达式x + 150
由于有符号整数溢出而导致未定义行为。语言定义中没有提到ALU或CPU,因此我们可以确定这些事情与行为是否未定义无关。
如果是,编译器如何检测溢出?
没有必要。确切地说,因为行为是未定义的,这意味着编译器不必担心溢出时会发生什么。它只需要发出一个可执行文件,该可执行文件举例说明所定义的情况的行为。
在这个程序中,这些是范围[INT_MIN
,INT_MAX-150
]中的输入,因此编译器可以将比较转换为x < -145
,因为这对定义良好的范围中的所有输入都有相同的行为,并且与未定义的情况无关。
- 编译C++时未定义的引用
- 自定义 std::fstream,std::filebuf 的溢出和下溢函数未为每个字符调用
- Cygwin下的gcc 9.3.0预处理器:cmdline -Dname,但名称似乎未定义
- 监视 SDK 下的未定义符号"__Unwind_SjLj_Unregister"
- 上溢/下溢是执行时未定义的行为吗
- 我如何在不将所有相关代码带到标头的情况下解决模板成员函数的未定义引用
- 为什么在包含类头的情况下,我的数组在main中未定义
- 这是一种未定义的行为吗?我可以在没有温度的情况下交换值吗?
- 在启用 SSL 的情况下构建 libircclient 时对 'DLopen' 的未定义引用
- 收到错误"二进制'[':"const typ"未定义此运算符"或"下标需要数组或指针类型
- 编译器如何优化不正确C++分层向下转换以导致真正的未定义行为
- 为什么C++下溢/溢出行为被视为未定义
- 这是未定义的下转换吗
- 在对象文件中具有未定义符号类型的程序如何在没有任何链接器错误的情况下进行编译"U"?
- 在溢出的情况下,i++是否对小于int的有符号类型调用未定义的行为?
- 对Logger::getInstance()的未定义引用-但仅在某些情况下
- Ubuntu 下的 C++ 链接 Boost 库与 cMake:未定义的引用 'boost::iostreams::zlib::ok'
- 未签名的下溢机制
- 节点中的未定义符号.js C++Linux下的插件,为什么
- 窗口下的外部函数调用进行未定义的引用