未定义的行为(Undefined Behavior),指程序不可预测的执行效果,一般由错误的代码实现引起。出于效率、兼容性等多方面原因,语言标准不便于定义错误程序的明确行为,而是将其统称为“未定义”的行为,可以是崩溃,也可以是非预期的任意表现,有些问题在编译器和执行环境的特殊处理下也可能不会造成实质性的危害,但不具备可移植性。代码质量管理的一个重要目标就是消除会导致未定义行为的代码。
C++ 标准声明了导致未定义行为的情况,本文梳理了 ISO/IEC 14882:2003 和 ISO/IEC 14882:2011 前 18 章的相关内容,后 12 章的主题为标准库实现,相关内容的主旨在前 18 章中已有体现,C 语言相关内容可参见《C 未定义行为成因列表》。
如果代码行以反斜杠结尾,预处理器会删除行尾的反斜杠和换行符,使其与下一行连接,如果连接后产生了以 \u 或 \U 开头的通用字符名称(universal character name)会导致未定义的行为。
示例:
auto s = "\u54\
0D"; // Undefined behavior
auto \u54\
0D = 'a'; // Undefined behavior
这种代码可能不会通过编译,也可能不会造成实际问题,但一定不具备可移植性。
ISO/IEC 14882:2003 2.1(2)-undefined
ISO/IEC 14882:2011 2.2(2)-undefined
示例:
namespace NS {
....
}\ // Undefined behavior if this is the last line
C++03 声明了这种情况会导致未定义的行为,C++11 规定在这种情况下编译器应补全所需的空行。
ISO/IEC 14882:2003 2.1(2)-undefined
ID_missingNewLineFileEnd
ID_badBackslash
预处理运算符 ## 可以将两个符号连接成一个符号,如果产生了以 \u 或 \U 开头的通用字符名称会导致未定义的行为。
示例:
#define M(a, b) a ## b
int M(\u54, 0d) = 123; // Undefined behavior
ISO/IEC 14882:2003 2.1(4)-undefined
ISO/IEC 14882:2011 2.2(4)-undefined
预处理器连接以反斜杠结尾的各行代码后将其转为预处理符号序列,在处理指令和展开宏之前,如果出现了不符合词法的单引号或双引号,会导致未定义的行为。
示例:
#defined X ' // Undefined behavior
#defined Y " // Undefined behavior
例中的引号无法与其他字符组成预处理符号,可能不会通过编译,也可能产生非预期的结果。
ISO/IEC 14882:2003 2.4(2)-undefined
ISO/IEC 14882:2011 2.5(2)-undefined
示例:
#include <"foo"> // Undefined behavior in C++03
#include "foo//bar" // Undefined behavior in C++03
程序在这种情况下的行为在 C++03 中是未定义的,在 C++11 中是由实现定义的。
ISO/IEC 14882:2003 2.8(2)-undefined
ISO/IEC 14882:2011 2.9(2)-implementation
ID_nonStandardCharInHeaderName
ID_forbidBackslashInHeaderName
示例:
cout << 2147483648; // Undefined behavior in C++03
如果 long int 为 32 位有符号整数类型,字面常量 2147483648 超出了范围,其行为在 C++03 中是未定义的,在 C++11 中则会将 2147483648 归为 unsigned long int 类型。
ISO/IEC 14882:2003 2.13.1(2)-undefined
标准转义字符:
\a // Alert
\b // Backspace
\f // Formfeed page break
\n // New line
\r // Carriage return
\t // Horizontal tab
\v // Vertical tab
\\ // Backslash
\? // Question mark
\' // Single quotation mark
\" // Double quotation mark
\0 // Null character
\ddd // Any character, ‘d’ is an octal number
\xhh // Any character, ‘h’ is a hex number
反斜杠后如果出现其他形式的字符或字符序列会导致未定义的行为。
ISO/IEC 14882:2003 2.13.2(3)-undefined
ISO/IEC 14882:2011 2.14.3(3)-implementation
示例:
*((char*)"oops") = 'O'; // Undefined behavior, may crash
cout << "oops"; // If it doesn't crash, this might output ‘Oops’
修改字面常量是一种逻辑错误。多数通用系统会在运行时保护常量数据,相关修改会造成崩溃,在没有这种保护机制的系统中,字符串常量可能会被修改,但也可能会影响到其他相同的字符串常量,因为相同的字符串常量可能共用相同的存储空间。
ISO/IEC 14882:2003 2.13.4(2)-undefined
ISO/IEC 14882:2011 2.14.5(12)-undefined
示例:
auto* x = L"123" "456"; // Undefined in C++03
auto* y = L"123" "456"; // A wide string in C++11
C++03 规定宽字符串与窄字符串连接会导致未定义的行为。C++11 规定一个字符串有前缀一个没有的话,结果以有前缀的为准,其他情况由实现定义。
ISO/IEC 14882:2003 2.13.4(3)-undefined
ISO/IEC 14882:2011 2.14.5(13)-implementation
任何翻译单元不得包含变量、函数、类型、枚举或模板的多个定义,即 One Definition Rule,违反此规则会导致未定义的行为。
示例:
// In a.cpp
struct T {
int i;
};
// In b.cpp
struct T {
long i; // Undefined behavior, volates One Definition Rule
};
ISO/IEC 14882:2003 3.2(5)-undefined
ISO/IEC 14882:2011 3.2(5)-undefined
程序调用 exit 函数后,全局、静态或 thread_local 对象开始析构,而这种对象的析构函数再调用 exit 会导致未定义的行为。
示例:
class T {
....
public:
~T() {
std::exit(1); // Dangerous
}
};
static T obj; // Undefined behavior during destruct
例中 obj 对象在析构时会导致未定义的行为。
ISO/IEC 14882:2003 3.6.1(4)-undefined
ISO/IEC 14882:2011 3.6.1(4)-undefined
示例:
// In T.cpp
void foo() {
static T sObj;
....
}
// In U.cpp
class U {
....
public:
~U() {
foo(); // Undefined behavior if ‘sObj’ is destructed
}
};
U gObj; // Problematic
当例中全局对象 gObj 析构时会调用 foo 函数,如果这时 foo 函数中的静态对象 sObj 已析构,会导致未定义的行为,gObj 与 sObj 的析构顺序在标准中是不确定的。
ISO/IEC 14882:2003 3.6.3(2)-undefined
ISO/IEC 14882:2011 3.6.3(2)-undefined
示例:
extern T obj;
void foo() {
obj.fun(); // Undefined behavior if ‘obj’ is destructed
}
当全局对象 obj 析构之后,再调用 foo 函数会导致未定义的行为(如在另一个全局对象的析构函数中调用 foo 函数)。
ISO/IEC 14882:2011 3.6.3(4)-undefined
示例:
void foo(size_t n) {
int* p = new int[n];
....
*p = 123; // Undefined behavior if ‘n’ is zero
....
delete[] p;
}
如果例中参数 n 为 0,对 p 的解引用会导致未定义的行为。
ISO/IEC 14882:2003 3.7.3.1(2)-undefined
ISO/IEC 14882:2011 3.7.4.1(2)-undefined
示例:
class A {
....
public:
void operator delete(void* p) {
if (!p) {
throw Exception(); // Undefined behavior
}
....
}
};
在 delete 运算符中抛出异常会导致未定义的行为。
ISO/IEC 14882:2011 3.7.4.2(3)-undefined
示例:
T* p = new T;
....
free(p); // Undefined behavior
例中用 free 释放由 new 分配的内存空间,会导致未定义的行为。
ISO/IEC 14882:2011 3.7.4.2(3)-undefined
示例:
int* p = new int(1);
delete p; // Well-defined
delete p; // Undefined behavior
cout << *p; // Undefined behavior
指针指向的对象被回收后,指针的值和指针曾指向的对象均失效,继续访问会导致未定义的行为。
ISO/IEC 14882:2003 3.7.3.2(4)-undefined
ISO/IEC 14882:2011 3.7.4.2(4)-undefined
ID_illAccess
ID_doubleFree
ID_danglingDeref
示例:
struct U {
....
};
struct T {
~T(); // If it has side effects
....
};
void* p = malloc(max(sizeof(T), sizeof(U)));
T* pT = new (p) T;
U* pU = new (p) U; // Undefined behavior
例中第二个 new 表达式结束了 pT 所指对象的生命周期,但没有调用其析构函数,如果其析构函数存在副作用,则会导致未定义的行为。
ISO/IEC 14882:2003 3.8(4)-undefined
ISO/IEC 14882:2011 3.8(4)-undefined
在对象的生命周期之外,通过指针进行如下操作会导致未定义的行为:
- 访问非静态成员函数
- 将指针转为基类指针
- 用 static_cast 转换指针(转为 void*、char*、unsigned char* 等情况除外)
- 用 dynamic_cast 转换指针
示例:
struct T {
T();
~T();
void fun();
};
T* p = (T*)malloc(sizeof(T));
p->fun(); // Undefined behavior, the lifetime has not yet started
new (p) T();
p->fun(); // Well-defined
p->~T();
p->fun(); // Undefined behavior, the lifetime has ended
free(p);
ISO/IEC 14882:2003 3.8(5)-undefined
ISO/IEC 14882:2011 3.8(5)-undefined
在对象的生命周期之外,通过 glvalue 进行如下操作会导致未定义的行为:
- 进行 lvalue-to-rvalue 转换
- 访问非静态成员函数
- 向基类引用转换
- 用 static_cast 转换 glvalue(转为 char& 或 unsigned char& 等情况除外)
- 用 dynamic_cast 转换 glvalue
- 将 typeid 作用于 glvalue
示例:
struct B { void foo(); };
struct D: B { .... };
void B::foo() {
new (this) D; // Ends the lifetime of *this
}
void bar(B& b) {
b.foo();
if (typeid(b) == ....) { // Undefined behavior
....
}
}
例中 b.foo 执行后 b 的生命周期结束,之后再对 b 的访问会导致未定义的行为。
ISO/IEC 14882:2003 3.8(6)-undefined
ISO/IEC 14882:2011 3.8(6)-undefined
示例:
struct A { ~A(); };
struct B { ~B(); };
void foo() {
A a;
new (&a) B;
} // Undefined behavior
例中局部对象 a 的空间被不相关类型的对象占据,在 foo 函数返回前仍会调用 A 的析构函数,导致未定义的行为。
ISO/IEC 14882:2003 3.8(8)-undefined
ISO/IEC 14882:2011 3.8(8)-undefined
示例:
struct T {
T();
~T();
};
const T obj;
void foo() {
obj.~T();
new (&obj) const T; // Undefined behavior
}
ISO/IEC 14882:2003 3.8(9)-undefined
ISO/IEC 14882:2011 3.8(9)-undefined
只应通过以下类型的 glvalue 访问对象,否则导致未定义的行为:
- 对象的动态类型
- 用 const 或 volatile 限定的对象动态类型
- 与对象动态类型相似的类型(参见 ISO/IEC 14882:2011 4.4)
- 与对象动态类型对应的有符号或无符号类型
- 用 const 或 volatile 限定的与对象动态类型对应的有符号或无符号类型
- 包含上述类型的聚合或联合类型(不包括静态成员类型)
- 对象动态类型的基类类型(也包括被 const 或 volatile 限定的基类类型)
- char 或 unsigned char
示例:
int i = 0;
cout << i; // Well-defined
cout << (const int&)i; // Well-defined
cout << (unsigned int&)i; // Well-defined
cout << (char&)i; // Well-defined
cout << (long&)i; // Undefined behavior
cout << (float&&)i; // Undefined behavior
ISO/IEC 14882:2003 3.10(15)-undefined
ISO/IEC 14882:2011 3.10(10)-undefined
glvalue 的类型与其引用的对象类型不同且没有继承关系,或引用的对象未初始化,会导致未定义的行为。
示例:
struct A { int i; };
struct B { int i; };
int foo(A& a) {
return ((B&)a).i; // Undefined behavior, unrelated type conversion
}
int foo() {
int i;
return i; // Undefined behavior, ‘i’ is not initialized
}
ISO/IEC 14882:2003 4.1(1)-undefined
ISO/IEC 14882:2011 4.1(1)-undefined
ID_castNoInheritance
ID_localInitialization
示例:
double d = FLT_MAX;
d = d * 10;
float f = d; // Non-defined behavior
例中 d 的值超过了 float 的取值范围,将 d 的值转为 float 会导致未定义的行为。
ISO/IEC 14882:2003 4.8(1)-undefined
ISO/IEC 14882:2011 4.8(1)-undefined
示例:
double f = 1e60;
signed i = f; // Undefined behavior
例中 1e60 无法被整型变量存储,导致未定义的行为。
ISO/IEC 14882:2003 4.9(1)-undefined
ISO/IEC 14882:2011 4.9(1)-undefined
示例:
long n = 1234567890L;
float f0 = n; // Cannot be represented exactly
double f1 = n; // OK
例中 1234567890 无法被 float 变量存储,但可被 double 变量存储。
ISO/IEC 14882:2011 4.9(2)-undefined
示例:
int a = 0;
int b = a + a++; // Undefined behavior
例中加法运算符左右子表达式无明确的求值顺序,如果左子表达式先求值,b 的值是 0,如果右子表达式先求值,b 的值可能是 1 也可能是 0,因为 a++ 的值是 0,但 a++ 的副作用在表达式求值过程中何时生效也是不确定的。
又如:
volatile int* p = foo();
int n = *p + *p; // Undefined behavior
例中 n 的值在数学上应是 *p 的二倍,但由于 p 指向 volatile 数据,结果可能不符合预期的数学关系。
ISO/IEC 14882:2003 5(4)-undefined
ID_evaluationOrderReliance
ID_confusingAssignment
如除 0、有符号整数溢出、负数位运算、浮点异常等均会导致未定义的行为。
示例:
signed s = INT_MAX + 1; // Undefined
unsigned u = UINT_MAX + 1; // Well-defined
例中变量 u 的值一定是 0,而 s 的值是标准未定义的,往往由编译器和执行环境决定。
设无符号整数的最大值为 M,无符号整数运算在程序中的结果是数学上的结果与 (M + 1) 取模的结果,这在数学上是有明确定义的,而对于有符号整数,将符号位移出相关比特位,或将非符号位移入符号位,在数学上是没有意义的,故称无符号整数不存在溢出问题,有符号整数存在溢出问题,溢出会导致未定义的行为。
ISO/IEC 14882:2003 5(5)-undefined
ISO/IEC 14882:2011 5(4)-undefined
ID_divideByZero
ID_evalOverflow
示例:
// In a.c
int foo() {
return 0;
}
// In b.cpp
int foo(); // Missing extern "C"
int bar() {
return foo(); // Undefined behavior
}
ISO/IEC 14882:2003 5.2.2(1)-undefined
ISO/IEC 14882:2011 5.2.2(1)-undefined
可变参数列表是 C 语言的概念,C++ 中具有拷贝构造或析构函数的对象难以与其兼容,如果将非 POD 对象传入可变参数列表,程序的行为在 C++03 中是未定义的,在 C++11 中是部分由实现定义的。
示例:
string str;
void foo(int, ...);
foo(1, str); // Undefined behavior
ISO/IEC 14882:2003 5.2.2(7)-undefined
ISO/IEC 14882:2011 5.2.2(7)-implementation
ID_nonPODVariadicArgument
ID_badVaArgType
ID_forbidVariadicFunction
示例:
struct A {};
struct B: A {};
struct C: virtual B {};
A a;
C c;
A& ra = a;
A& rc = c;
static_cast<B&>(ra); // Undefined behavior
static_cast<C&>(rc); // Undefined behavior
例中 ra 引用的是基类对象,将其转为派生类引用会导致未定义的行为,A 和 B 是 C 的虚基类,需要运行时数据体现虚基类对象和派生类对象的空间关系,static_cast 不考虑与运行时相关的转换逻辑,无法正确转换。
ISO/IEC 14882:2003 5.2.9(5)-undefined
ISO/IEC 14882:2011 5.2.9(2)-undefined
示例:
struct A {};
struct B: A {};
struct C: virtual B {};
A a;
C c;
A* pa = &a;
A* pc = &c;
static_cast<B*>(pa); // Undefined behavior
static_cast<C*>(pc); // Undefined behavior
例中 pa 指向基类对象,将其转为派生类指针会导致未定义的行为,A 和 B 是 C 的虚基类,需要运行时数据体现虚基类对象和派生类对象的空间关系,static_cast 不考虑与运行时相关的转换逻辑,无法正确转换。
ISO/IEC 14882:2003 5.2.9(8)-undefined
ISO/IEC 14882:2011 5.2.9(11)-undefined
示例:
struct B { int b; };
struct D: B { int d; };
int D::* mpb = &D::b;
int D::* mpd = &D::d;
static_cast<int B::*>(mpb); // OK
static_cast<int B::*>(mpd); // Undefined behavior
例中基类没有成员 d,将指向成员 d 的成员指针转为基类成员指针会导致未定义的行为。
ISO/IEC 14882:2003 5.2.9(9)-undefined
ISO/IEC 14882:2011 5.2.9(12)-undefined
示例:
void foo();
return ((int(*)())(&foo))(); // Undefined behavior
例中 foo 函数没有返回值,将其强转为有返回值的函数并调用,会导致未定义的行为。
ISO/IEC 14882:2003 5.2.10(6)-undefined
ISO/IEC 14882:2011 5.2.10(6)-undefined
示例:
const int ci = 0;
const_cast<int&>(ci) = 1; // Undefined behavior
ISO/IEC 14882:2003 5.2.11(7)-undefined
ISO/IEC 14882:2011 5.2.11(7)-undefined
示例:
struct T; // Incomplete type
T* foo(T& obj) {
return &obj; // Undefined behaviour
}
struct T {
T* operator &(); // Overload
};
在 foo 函数中参数 obj 的类型是不完整的,但其完整类型重载了 operator &,导致未定义的行为。
ISO/IEC 14882:2003 5.3.1(4)-undefined
ISO/IEC 14882:2011 5.3.1(5)-undefined
示例:
int* foo(int n) {
return new int[n]; // Undefined in C++03 if ‘n’ is negtive
}
如果 n 为负数,程序的行为在 C++03 中是未定义的,C++11 去掉了这项未定义行为的声明。
ISO/IEC 14882:2003 5.3.4(6)-undefined
示例:
string object;
delete &object; // Undefined behavior
delete "string"; // Undefined behavior
delete new int[n]; // Undefined behavior
delete[] new int(n); // Undefined behavior
ISO/IEC 14882:2003 5.3.5(2)-undefined
ISO/IEC 14882:2011 5.3.5(2)-undefined
ID_excessiveDelete
ID_insufficientDelete
ID_incompatibleDealloc
ID_illDealloc
用 delete 释放对象时,对象的静态类型应与动态类型兼容,如果静态类型是动态类型的基类,静态类型应提供虚析构函数,否则导致未定义的行为;用 delete[] 释放数组时,对象的静态类型应与动态类型一致,否则导致未定义的行为。
示例:
struct B { ~B(); };
struct D: B { ~D(); };
B* pDObj = new D;
B* pDArr = new D[123];
delete pDObj; // Undefined behavior
delete[] pDArr; // Undefined behavior
例中基类 B 缺少虚析构函数,pDArr 的类型应为派生类指针。
ISO/IEC 14882:2003 5.3.5(3)-undefined
ISO/IEC 14882:2011 5.3.5(3)-undefined
ID_missingVirtualDestructor
ID_arrayPointerCast
示例:
struct T;
void foo(T* p) {
delete p; // Undefined behavior
}
struct T {
~T(); // Non-trivial destructor
};
例中 delete 作用于不完整类型的指针 p,会导致未定义的行为。
ISO/IEC 14882:2003 5.3.5(5)-undefined
ISO/IEC 14882:2011 5.3.5(5)-undefined
示例:
struct A { int a; };
struct B { int a, b; };
int foo(A* pa) {
B* pb = (B*)pa;
int B::* m = &B::b;
return pb->*m; // Undefined behavior
}
ISO/IEC 14882:2003 5.5(4)-undefined
ISO/IEC 14882:2011 5.5(4)-undefined
示例:
struct T { .... };
int foo(T& obj) {
int T::* mp = nullptr;
return obj.*mp; // Undefined behavior
}
ISO/IEC 14882:2003 5.5(6)-undefined
ISO/IEC 14882:2011 5.5(6)-undefined
示例:
int foo(int a, int b) try
{
return a / b; // Undefined behavior if ‘b’ is zero
}
catch (...)
{
return 0; // Unreachable
}
除数为 0 会导致未定义的行为,且不受语言的异常机制控制。
ISO/IEC 14882:2003 5.6(4)-undefined
ISO/IEC 14882:2011 5.6(4)-undefined
示例:
int foo() {
int a[10];
....
int* p = a + 9; // OK
return p[1]; // Undefined behavior
}
例中 p[1] 相当于 *(p + 1),即 a[10],超过了数组最后一个元素的地址,导致未定义的行为。
ISO/IEC 14882:2003 5.7(5)-undefined
ISO/IEC 14882:2011 5.7(5)-undefined
ID_bufferOverflow
ID_arrayIndexOverflow
示例:
int a[10];
int b[10];
ptrdiff_t d = a - b; // Undefined behavior
ISO/IEC 14882:2003 5.7(5)-undefined
ISO/IEC 14882:2011 5.7(5)-undefined
示例:
int a = 1 << -1; // Undefined behavior
int b = 1 << 100; // Undefined behavior
ISO/IEC 14882:2003 5.8(1)-undefined
ISO/IEC 14882:2011 5.8(1)-undefined
将符号位移出相关比特位,或将非符号位移入符号位,在数学上是没有意义的。
示例:
int b = -1 << 1; // Undefined
编译器可能会用乘法或除法实现有符号整数的移位,但不属于标准行为。
ISO/IEC 14882:2011 5.8(2)-undefined
示例:
struct T {
int a[10];
};
struct W {
int i;
T t;
};
union U {
T t;
W w;
};
void foo(U& u) {
u.w.t = u.t; // Undefined behavior
}
例中 u.w.t 和 u.t 具有部分重叠区域,导致未定义的行为。
ISO/IEC 14882:2003 5.17(8)-undefined
ISO/IEC 14882:2011 5.17(8)-undefined
示例:
bool foo(int i) {
if (i > 10) {
return true;
}
} // Undefined behavior if i <= 10
如果例中 i 的值小于或等于 10,函数将返回一个不确定的值,使用这种不确定的值会导致未定义的行为。
ISO/IEC 14882:2003 6.6.3(2)-undefined
ISO/IEC 14882:2011 6.6.3(2)-undefined
示例:
int foo(int i) {
if (i < 100) {
static int s = foo(2 * i); // Undefined behavior
return s;
}
return i;
}
例中静态局部变量 s 的初始化需要递归调用所属函数,是否会产生多个 s 的实例以及是否能正常结束递归,均是未定义的。
ISO/IEC 14882:2003 6.7(4)-undefined
ISO/IEC 14882:2011 6.7(4)-undefined
示例:
struct T {
mutable int i;
int j;
};
const T obj;
obj.i++; // Well-defined
obj.j++; // Ill-formed
T* p = (T*)&obj;
p->i = 0; // Well-defined
p->j = 1; // Undefined behavior
ISO/IEC 14882:2003 7.1.5.1(4)-undefined
ISO/IEC 14882:2011 7.1.6.1(4)-undefined
示例:
int foo(int&);
volatile int* bar();
foo((int&)*bar()); // Undefined behavior
ISO/IEC 14882:2003 7.1.5.1(7)-undefined
ISO/IEC 14882:2011 7.1.6.1(6)-undefined
示例:
[[noreturn]] void foo() {
throw Exception(); // OK
}
[[noreturn]] int bar() {
return 0; // Undefined behavior
}
[[noreturn]] void baz(int i) { // Undefined behavior if ‘i’ != 0
if (i == 0) {
throw Exception();
}
}
例中 bar 可以正常返回,baz 在某条件下正常返回,会导致未定义的行为。
ISO/IEC 14882:2011 7.6.3(2)-undefined
空指针表示没有指向任何对象的指针,通过空指针访问对象属于逻辑错误。
示例:
struct T {
int i;
int foo() { return i; }
int bar() { return 0; }
static int baz();
};
T* p = nullptr;
p->foo(); // Undefined behavior
p->bar(); // Undefined behavior
p->baz(); // Well-defined, ‘baz’ is a static member
通过空指针调用对象的非静态成员函数会导致未定义的行为,即使成员函数没有引用成员数据。通过空指针调用静态成员函数不属于访问对象,所以这种情况不属于逻辑错误。
ISO/IEC 14882:2003 8.3.2(4)-undefined
ISO/IEC 14882:2011 8.3.2(5)-undefined
ID_nullDerefInScp
ID_nullDerefInExp
示例:
struct A { int foo(); };
struct B { .... };
int bar(void* p) {
return ((A*)p)->foo();
}
int main() {
B b;
return bar(&b); // Undefined behavior
}
例中 A 与 B 是不同且没有继承关系的类,通过不合理的类型转换调用非静态成员函数会导致未定义的行为。
ISO/IEC 14882:2003 9.3.1(1)-undefined
ISO/IEC 14882:2011 9.3.1(2)-undefined
ID_castNoInheritance
ID_forbidMemberVoidPtr
ID_forbidFunctionVoidPtr
示例:
struct T {
virtual ~T() {
release(); // Undefined behavior
}
virtual void release() = 0;
};
ISO/IEC 14882:2003 10.4(6)-undefined
ISO/IEC 14882:2011 10.4(6)-undefined
ID_virtualCallInConstructor
ID_virtualCallInDestuctor
示例:
void foo(void* p) {
delete (T*)p; // Undefined if delete an object of type other than T
}
如果例中 p 指向的不是 T 类型的对象会导致未定义的行为。
ISO/IEC 14882:2003 12.4(12)-undefined
ISO/IEC 14882:2011 12.4(13)-undefined
ID_forbidFunctionVoidPtr
ID_forbidMemberVoidPtr
ID_castNoInheritance
示例:
struct T { ~T(); };
void foo() {
T obj;
obj.~T(); // End of lifetime
} // Undefined behavior
显式调用 obj 的析构函数后,obj 的生命周期结束,但函数返回前还会调用 obj 的析构函数,导致未定义的行为。
又如:
struct B { .... };
struct D: B { .... };
D* p = new D;
p->B::~B(); // End of lifetime of base class object
delete p; // Undefined behavior
调用 p->B::~B() 后,基类对象的生命周期结束,但在 delete p 时基类析构函数仍会被执行,导致未定义的行为。
ISO/IEC 14882:2003 12.4(14)-undefined
ISO/IEC 14882:2011 12.4(15)-undefined
示例:
struct A {
A(int);
};
struct B: A {
int i;
int fun();
B(): A(fun()), // Undefined
i(fun()) { // Well-defined
}
};
例中成员函数 fun 的返回值是基类构造函数的参数,但基类尚未开始构造,会导致未定义的行为,用成员函数 fun 初始化成员 i 则没有问题,因为此时基类对象已构造完毕。
ISO/IEC 14882:2003 12.6.2(8)-undefined
ISO/IEC 14882:2011 12.6.2(13)-undefined
对具有 non-trivial 构造函数的对象,在构造函数执行之前引用其非静态成员或基类对象,或者在析构函数执行之后引用其非静态成员或基类对象,会导致未定义的行为。
示例:
struct A { // Trivial
int a;
};
struct B: A { // Non-trivial
B();
int b;
};
extern B obj; // The object is defined later
B* p = &obj; // OK
int* p1 = &obj.a; // Undefined, refers to base class member
int* p2 = &obj.b; // Undefined, refers to member
A* pa = &obj; // Undefined, upcast to a base class type
extern A trvlObj; // The object is defined later
int* p3 = &trvlObj.a; // OK, A is a trivial class
B obj; // Define the object
A trvlObj; // Define the object
ISO/IEC 14882:2003 12.7(1)-undefined
ISO/IEC 14882:2011 12.7(1)-undefined
示例:
struct A {
....
};
struct B: A {
B();
B(A*);
};
struct C: B {
C(): B(this) { // Undefined behavior
}
};
例中将 C 的 this 指针作为基类 B 构造函数的参数,相当于将 C* 转为 A*,而这时基类 B 和 A 均未开始构造,会导致未定义的行为。
又如(各项声明接上例):
struct X {
X(A*);
};
struct D: B, X {
D(): B(), X(this) { // OK
}
};
将 D 的 this 指针作为基类 X 构造函数的参数,相当于将 D* 转为 A*,这时 B 的构造函数已经执行完毕,所以这种情况没有问题。
ISO/IEC 14882:2003 12.7(2)-undefined
ISO/IEC 14882:2011 12.7(3)-undefined
示例:
struct V {
virtual void foo();
virtual void bar();
};
struct A: virtual V {
void foo() override;
};
struct B: virtual V {
B(V*, A*);
void bar() override;
};
struct C: A, B {
C(): B((A*)this, this) {
}
void foo() override;
void bar() override;
};
B::B(V* v, A* a) {
v->bar(); // Well-defined, V is the base of B
a->foo(); // Undefined behavior, A is not a base of B
}
ISO/IEC 14882:2003 12.7(3)-undefined
ISO/IEC 14882:2011 12.7(4)-undefined
示例:
struct V {
virtual void foo();
};
struct A: virtual V {};
struct B: virtual V { B(V*, A*); };
struct C: A, B {
C(): B((A*)this, this) {}
};
B::B(V* v, A* a) {
typeid(*v); // Well-defined, V is the base of B
typeid(*a); // Undefined behavior, A is not a base of B
}
ISO/IEC 14882:2003 12.7(4)-undefined
ISO/IEC 14882:2011 12.7(5)-undefined
示例:
struct V {
virtual void foo();
};
struct A: virtual V {};
struct B: virtual V { B(V*, A*); };
struct C: A, B {
C(): B((A*)this, this) {}
};
B::B(V* v, A* a) {
dynamic_cast<B*>(v); // Well-defined, V is the base of B
dynamic_cast<B*>(a); // Undefined behavior, A is not a base of B
}
ISO/IEC 14882:2003 12.7(5)-undefined
ISO/IEC 14882:2011 12.7(6)-undefined
示例:
namespace N
{
struct A {};
template <class T>
void foo(T&, double) { .... }
}
int main()
{
N::A a;
foo(a, 1);
}
namespace N
{
template <class T>
void foo(T&, int) { .... } // Undefined behaivor, a better match for ‘foo(a, 1)’
}
ISO/IEC 14882:2003 14.6.4.2(1)-undefined
ISO/IEC 14882:2011 14.6.4.2(1)-undefined
示例:
template <class T>
class A
{
A<T>* p; // OK
A<T*> a; // Undefined behavior
};
如果实例化 A,需要实例化 A<T*>,如果实例化 A<T*>,需要实例化 A<T**>,依次类推,形成了无限递归。
ISO/IEC 14882:2003 14.7.1(14)-undefined
ISO/IEC 14882:2011 14.7.1(15)-undefined
示例:
class T {
int err;
public:
T() try { .... } catch (...) { log(err); } // Undefined behavior
~T() try { .... } catch (...) { log(err); } // Undefined behavior
};
流程进入 function-try-block 的 handler 时,非静态成员的生命周期已结束,不可再被访问。
ISO/IEC 14882:2003 15.3(10)-undefined
ISO/IEC 14882:2011 15.2(10)-undefined
示例:
int foo() try { // Function-try-block
return bar();
}
catch (...) { // Undefined behavior if there is no return statement
}
例中 function-try-block 的 handler 没有 return 语句,捕获异常后会返回不确定的值。
ISO/IEC 14882:2003 15.3(16)-undefined
ISO/IEC 14882:2011 15.3(15)-undefined
示例:
#define DEFINED(x) defined(x)
#if DEFINED(__cplusplus) // Undefined behavior
....
#endif
ISO/IEC 14882:2003 16.1(4)-undefined
ISO/IEC 14882:2011 16.1(4)-undefined
ID_macro_defineReserved
ID_illFormedDirective
示例:
#define STDIO_H <stdio.h>
#define STDLIB_H stdlib.h
#include STDIO_H // OK
#include STDLIB_H // Undefined behavior
ISO/IEC 14882:2003 16.2(4)-undefined
ISO/IEC 14882:2011 16.2(4)-undefined
示例:
#define PRINT(s) printf(#s)
PRINT(
#ifdef MAC // Undefined behavior
rabbit
#else
hamster
#endif
);
ISO/IEC 14882:2003 16.3(10)-undefined
ISO/IEC 14882:2011 16.3(11)-undefined
ID_directiveInMacroArgument
ID_macro_function
示例:
#define M(x) #x
cout << M(); // Undefined behavior
例中宏 M 的参数不足,无法生成有效的字符串,这可能不会通过编译,也可能产生空串,或其他非预期的结果。
ISO/IEC 14882:2003 16.3.2(2)-undefined
ISO/IEC 14882:2011 16.3.2(2)-undefined
示例:
#define M(a, b) a ## b
int M(x, 0) = 0; // OK, ‘x0’ is a valid token
int M(0, x) = 0; // Undefined behavior, ‘0x’ is not a valid token
ISO/IEC 14882:2003 16.3.3(3)-undefined
ISO/IEC 14882:2011 16.3.3(3)-undefined
C++03 规定行号不可大于 32767,C++11 规定不可大于 2147483647,否则导致未定义的行为。
示例:
#line 0 // Undefined behavior
#line 2147483648 // Undefined behavior
ISO/IEC 14882:2003 16.4(3)-undefined
ISO/IEC 14882:2011 16.4(3)-undefined
#line 后只应为整数常量或整数常量和文件名称字符串,其他形式均会导致未定义的行为。
示例:
#line "name.cpp" // Undefined behavior
#line NUM + 1234 // Undefined behavior
ISO/IEC 14882:2003 16.4(5)-undefined
ISO/IEC 14882:2011 16.4(5)-undefined
如 __LINE__、__FILE__、__DATE__、__TIME__、__STDC__、__cplusplus、defined 等名称被定义或取消定义。
示例:
#undef __cplusplus // Undefined behavior
#define __STDC__ 0 // Undefined behavior
ISO/IEC 14882:2003 16.8(3)-undefined
ISO/IEC 14882:2011 16.8(4)-undefined
ID_macro_defineReserved
ID_macro_undefReserved
如 replacement functions 或 handler functions 不符合要求(required behavior),会导致未定义的行为。
示例:
struct T {
void* operator new(size_t) { // A replacement function
return nullptr; // Undefined behavior
}
};
标准要求 operator new 返回非空地址,分配失败则抛出 bad_alloc 异常(见 ISO/IEC 14882:2011 18.6.1.1),否则导致未定义的行为。
又如:
void my_handler() { // A handler function
return; // Do nothing
}
int main() {
set_terminate(my_handler); // Undefined behavior
....
}
标准要求 terminate handler 结束进程的执行,不可返回至调用方(见 ISO/IEC 14882:2011 18.8.3.1),否则导致未定义的行为。
ISO/IEC 14882:2003 17.1.15-undefined
ISO/IEC 14882:2011 17.3.21-undefined
示例:
namespace std
{
const int& max(const int& a, const int& b) { // Undefined behavior
....
}
}
示例代码实现的 max 会干扰标准库中的 max,导致未定义的行为。
ISO/IEC 14882:2003 17.1.17-undefined
ISO/IEC 14882:2011 17.3.22-undefined
示例:
namespace std
{
using tstring = basic_string<
TCHAR, char_traits<TCHAR>, allocator<TCHAR> // Undefined behavior
>;
}
对特殊命名空间的修改是否会生效,以及是否会造成非预期的影响,均是未定义的。
ISO/IEC 14882:2011 17.6.4.2.1(1)-undefined
示例:
template <>
void std::vector<int>::push_back(const int&) { // Undefined behavior
....
}
示例代码特化了 vector 类的 push_back 函数,其产生的影响是未定义的。
ISO/IEC 14882:2011 17.6.4.2.1(2)-undefined
示例:
namespace posix { // Undefined behavior
....
}
posix 命名空间是 C++ 的保留命名空间,自定义代码中不应出现名为 posix 的命名空间。
ISO/IEC 14882:2011 17.6.4.2.2(1)-undefined
示例:
#define override // Undefined behavior
#define new new(nothrow) // Undefined behavior
#define string vector<char> // Undefined behavior
ISO/IEC 14882:2003 17.4.3.1(1)-undefined
ISO/IEC 14882:2011 17.6.4.3(2)-undefined
ID_reservedName
ID_macro_defineReserved
应选用健全的编译器,并保证编译环境的安全,否则程序的行为是未定义的。
ISO/IEC 14882:2003 17.4.3.2(1)-undefined
ISO/IEC 14882:2011 17.6.4.4(1)-undefined
ISO/IEC 14882:2003 17.4.3.7(1)-undefined
ISO/IEC 14882:2011 17.6.4.9(1)-undefined
某些库函数与共享数据相关,但并未提供安全的同步机制,在多线程环境中使用会导致未定义的行为。
示例:
void foo() {
while (true) {
auto r = rand(); // Undefined behavior
....
}
}
int main() {
thread t0(foo), t1(foo);
....
}
多线程并发调用 rand、srand 等函数会造成数据竞争,导致未定义的行为。
ISO/IEC 14882:2011 17.6.4.10(1)-undefined
示例:
string s{"abc"};
cout << s[8]; // Undefined behavior
cout << s.at(8); // Well-defined, throw out_of_range
标准规定 string::at 在参数超出范围时抛出 out_of_range 异常,而 string::operator [] 的参数超出范围会导致未定义的行为。
ISO/IEC 14882:2003 17.4.3.8(1)-undefined
ISO/IEC 14882:2011 17.6.4.11(1)-undefined
示例:
struct T {
int* m;
T();
virtual ~T();
};
size_t s = offsetof(T, m); // Undefined behavior
例中 T 不是 standard layout 类型,用 offsetof 求成员 m 的偏移量会导致未定义的行为。
ISO/IEC 14882:2003 18.1(5)-undefined
ISO/IEC 14882:2011 18.2(4)-undefined
示例:
void foo(int& n, ...); // Undefined behavior
void foo(int f(), ...); // Undefined behavior
void foo(int a[10], ...); // Undefined behavior
void foo(char c, ...); // Undefined behavior
例中参数 n、f、a 分别为引用、函数和数组,会导致未定义的行为,c 与默认参数提升后的类型不符,也会导致未定义的行为。
ISO/IEC 14882:2003 18.7(3)-undefined
ISO/IEC 14882:2011 18.10(3)-undefined
ID_badParmN
ID_forbidVariadicFunction
示例:
void foo() {
string obj;
....
longjmp(buf, x); // Undefined behavior
}
例中局部对象 obj 的析构函数不会被执行,由此引发的问题会导致未定义的行为。
ISO/IEC 14882:2003 18.7(4)-undefined
ISO/IEC 14882:2011 18.10(4)-undefined