C++未定义行为罗列

(4 mins to read)

概述

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
2
3
4
int foo(int i) {
static int s = foo(i << 1);
return i + 1;
}

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