¶概述
A Guide to Undefined Behavior in C and C++
未定义行为就是一系列不应该进行的无厘头操作,如果进行,则可能发生任何事情。
未定义行为可以简化编译器的工作,优化性能。(比如不需要边界检查、除零、有符号整数溢出)编译器的义务只是考虑行为已有定义的情况,换句话说,它默认输入的程序是没有未定义行为的。一个程序中只要一个操作是未定义的,那整个程序的结果就是未定义的(ill-formed)。
优化器在它认为可以提高性能,而且不会改变程序的可见行为时,可以进行指令重排等优化。
C的一项设计理念是“信任程序员”。
UB is a program executed out of contractwhen designing a program, remember to define the domain of inputsdefine what happens for out-of-contract inputsprovide optional input validation tools to your users
1 | asm volatile ("" : : : "memory"); |
以上内嵌汇编表示可能修改整个内存,它使得在它之前的所有寄存器的值都被保存到了内存中,并且在它之后需要重新加载。
¶顺序点和副作用
- 顺序点:一个结算点,在该时刻前的求值和副作用都必须完成,才能进行后续的部分,包括完整表达式、逗号、三元运算符、逻辑运算符、函数调用等(用于确定执行熟悉怒的概念)
- 副作用:计算表达式时,除了取得值外,可能改变某些变量的值(比如递增,赋值运算符)
有副作用的操作只能按序执行(比如printf),但其它操作是可以重排的(这个副作用应该说是有依赖的副作用,比如a++,b++这两个表达式是没有依赖的,所以也可以重排),本质是只要单线程运行结果不变即可。举个例子,在一个除零操作前加一个printf,发生了除零,但是没有打印出printf,这是因为段错误不是副作用,是可以被重排的。
¶列表
- 解引用空指针、野指针、new(0)返回的指针
- memcpy重叠的buffer
- 转换指针为不兼容类型
- 缓冲区溢出
- 有符号数溢出
- 移位为负数或者过大
- 使用未初始化自动变量
- 在有返回值函数中不返回值
- 同一个符号有多个定义
- 在静态变量初始化时发生递归
- 为std命名空间加东西
- 没有副作用的死循环(如
while(true) {}
)
1 | int foo(int i) { |
¶UBSAN
UBSAN在编译阶段插桩,然后在运行阶段检查。因此只有执行到的代码才会被检查到。
1 | clang++ -fsanitize=undefined |
Please eliminate:
- uninitialized variales
- range errors
- nullptr dereferencing
- resource leaks
- dangling references
- unions (use variants)
- casts
- underflow and overflow
- data races
- subscripting raw pointers (use vector and span)
Many notions of safety
-
logic errors
-
resource leaks
-
concurrency errors
-
memory corruption
-
type errors
-
overflows and unanticipated conversions
-
timing errors
-
allocation unpredictability
-
termination errors
-
type safety: no type or resource violations
-
range: no pointer arithmetic; no nullptr dereference, span and vector range throw or terminate on violations
-
arithmetic: no overflow, no narrowing conversions, no implicit signed/unsigned conversions