- ">附录:C++ 未定义行为成因列表
- 1. 代码行以反斜杠结尾,且与下一行连接后生成通用字符名称">1. 代码行以反斜杠结尾,且与下一行连接后生成通用字符名称
- 2. 非空源文件未以换行符结尾,或以换行符结尾但换行符之前是反斜杠">2. 非空源文件未以换行符结尾,或以换行符结尾但换行符之前是反斜杠
- 3. 连接预处理符号时产生了通用字符名称">3. 连接预处理符号时产生了通用字符名称
- 4. 存在不符合词法的单引号或双引号">4. 存在不符合词法的单引号或双引号
- 5. 在 #include 指令中,’、”、\、//、/* 出现在定界符 < > 之间,或 ‘、\、//、/* 出现在定界符 “ 之间">5. 在 #include 指令中,’、”、\、//、/* 出现在定界符 < > 之间,或 ‘、\、//、/* 出现在定界符 “ 之间
- 6. 无后缀 10 进制整数字面常量超过 long int 取值范围">6. 无后缀 10 进制整数字面常量超过 long int 取值范围
- 7. 使用非标准转义字符">7. 使用非标准转义字符
- 8. 修改字符串字面常量">8. 修改字符串字面常量
- 9. 窄字符串与宽字符串连接">9. 窄字符串与宽字符串连接
- 10. 违反 One Definition Rule">10. 违反 One Definition Rule
- 11. 具有静态或线程存储期的对象在析构函数中调用 std::exit 函数">11. 具有静态或线程存储期的对象在析构函数中调用 std::exit 函数
- 12. 函数内具有静态或线程存储期的对象已析构,之后该函数又被调用并引用到已析构的对象">12. 函数内具有静态或线程存储期的对象已析构,之后该函数又被调用并引用到已析构的对象
- 13. 在对象析构之后使用对象">13. 在对象析构之后使用对象
- 14. 指针指向长度为 0 的内存空间并被解引用">14. 指针指向长度为 0 的内存空间并被解引用
- 15. 内存回收函数抛出异常">15. 内存回收函数抛出异常
- 16. 使用不匹配的方法分配回收资源">16. 使用不匹配的方法分配回收资源
- 17. 使用已被释放的指针">17. 使用已被释放的指针
- 18. 对象生命周期已结束,但未调用其有副作用的析构函数">18. 对象生命周期已结束,但未调用其有副作用的析构函数
- 19. 在分配空间后,生命周期开始前,或在生命周期结束后,回收空间前,通过指针访问对象">19. 在分配空间后,生命周期开始前,或在生命周期结束后,回收空间前,通过指针访问对象
- 20. 在分配空间后,生命周期开始前,或在生命周期结束后,回收空间前,通过 glvalue 访问对象">20. 在分配空间后,生命周期开始前,或在生命周期结束后,回收空间前,通过 glvalue 访问对象
- 21. 具有静态、线程或自动存储期和 non-trivial 析构函数的对象,其空间被非兼容类型的对象占据">21. 具有静态、线程或自动存储期和 non-trivial 析构函数的对象,其空间被非兼容类型的对象占据
- 22. 常量对象的空间或曾属于常量对象的空间被其他对象占据">22. 常量对象的空间或曾属于常量对象的空间被其他对象占据
- 23. 通过 glvalue 访问对象,但 glvalue 的类型不符合要求">23. 通过 glvalue 访问对象,但 glvalue 的类型不符合要求
- 24. 通过 glvalue 引用不相关类型的对象或未初始化的对象">24. 通过 glvalue 引用不相关类型的对象或未初始化的对象
- 25. 浮点类型转换产生的结果无法在相应的空间中表示">25. 浮点类型转换产生的结果无法在相应的空间中表示
- 26. 浮点类型转为整数类型时,整数类型无法存储浮点类型的整数部分">26. 浮点类型转为整数类型时,整数类型无法存储浮点类型的整数部分
- 27. 整数类型转为浮点类型时,浮点类型无法存储整数的值">27. 整数类型转为浮点类型时,浮点类型无法存储整数的值
- 28. 表达式求值依赖无确定顺序的副作用">28. 表达式求值依赖无确定顺序的副作用
- 29. 表达式的结果在数学上没有定义">29. 表达式的结果在数学上没有定义
- 30. 被调用函数的语言链接性与该函数定义的语言链接性不符">30. 被调用函数的语言链接性与该函数定义的语言链接性不符
- 31. 将非 POD 对象传入可变参数列表">31. 将非 POD 对象传入可变参数列表
- 32. 用 static_cast 将基类引用转为派生类引用,基类为虚基类,或引用的实际对象并非派生类对象">32. 用 static_cast 将基类引用转为派生类引用,基类为虚基类,或引用的实际对象并非派生类对象
- 33. 用 static_cast 将基类指针转为派生类指针,基类为虚基类,或指向的实际对象并非派生类对象">33. 用 static_cast 将基类指针转为派生类指针,基类为虚基类,或指向的实际对象并非派生类对象
- 34. 用 static_cast 将成员指针转为基类成员指针时,基类中没有相关成员">34. 用 static_cast 将成员指针转为基类成员指针时,基类中没有相关成员
- 35. 函数指针被转为不兼容的类型并执行">35. 函数指针被转为不兼容的类型并执行
- 36. 类型转换时去掉对象 const 属性并修改对象">36. 类型转换时去掉对象 const 属性并修改对象
- 37. 对不完整类型的对象取地址,但该对象的完整类型重载了 operator &">37. 对不完整类型的对象取地址,但该对象的完整类型重载了 operator &
- 38. new 运算符第一个数组维度的参数为负数">38. new 运算符第一个数组维度的参数为负数
- 39. 用 delete 释放数组漏写中括号,用 delete 释放对象多写中括号,用 delete 释放非 new 表达式的结果">39. 用 delete 释放数组漏写中括号,用 delete 释放对象多写中括号,用 delete 释放非 new 表达式的结果
- 40. 被 delete 释放的对象或数组类型不符合要求">40. 被 delete 释放的对象或数组类型不符合要求
- 41. 用 delete 释放不完整类型的对象,但在对象完整类型声明中有 non-trivial 析构函数">41. 用 delete 释放不完整类型的对象,但在对象完整类型声明中有 non-trivial 析构函数
- 42. 对象解引用成员指针时,对象的动态类型不包含成员指针引用的成员">42. 对象解引用成员指针时,对象的动态类型不包含成员指针引用的成员
- 43. 对象解引用成员指针时,成员指针为空指针">43. 对象解引用成员指针时,成员指针为空指针
- 44. / 或 % 运算符第二个操作数的值为 0">44. / 或 % 运算符第二个操作数的值为 0
- 45. 指针加减整数,结果超出了指针所在数组的地址范围">45. 指针加减整数,结果超出了指针所在数组的地址范围
- 46. 不在同一数组中的指针相减">46. 不在同一数组中的指针相减
- 47. 移位运算符右操作数为负数或超过相关类型比特位的数量">47. 移位运算符右操作数为负数或超过相关类型比特位的数量
- 48. 对有符号整数进行超出取值范围的左移运算">48. 对有符号整数进行超出取值范围的左移运算
- 49. 将对象赋值给具有重叠区域的对象">49. 将对象赋值给具有重叠区域的对象
- 50. 函数没有通过 return 语句返回明确的值,但函数返回值被使用">50. 函数没有通过 return 语句返回明确的值,但函数返回值被使用
- 51. 递归地定义和初始化静态对象">51. 递归地定义和初始化静态对象
- 52. 修改非 mutable 常量对象">52. 修改非 mutable 常量对象
- 53. 使用没有 volatile 限定的 glvalue 访问有 volatile 限定的对象">53. 使用没有 volatile 限定的 glvalue 访问有 volatile 限定的对象
- 54. 具有 noreturn 属性的函数返回至调用方">54. 具有 noreturn 属性的函数返回至调用方
- 55. 空指针解引用">55. 空指针解引用
- 56. 对象的实际类型与当前静态类型不相关,并调用其非静态成员函数">56. 对象的实际类型与当前静态类型不相关,并调用其非静态成员函数
- 57. 在构造函数或析构函数中调用纯虚函数">57. 在构造函数或析构函数中调用纯虚函数
- 58. 对象的实际类型与当前静态类型不相关,并调用其析构函数">58. 对象的实际类型与当前静态类型不相关,并调用其析构函数
- 59. 在对象生命周期结束后调用其析构函数">59. 在对象生命周期结束后调用其析构函数
- 60. 基类对象构造完毕之前调用成员函数">60. 基类对象构造完毕之前调用成员函数
- 61. 对成员或基类对象的不合理引用">61. 对成员或基类对象的不合理引用
- 62. 将对象指针转为其基类对象的指针时,基类对象尚未开始构造或已结束析构">62. 将对象指针转为其基类对象的指针时,基类对象尚未开始构造或已结束析构
- 63. 正在构造或析构的对象通过 . 或 -> 调用虚函数,而且该对象与当前构造或析构函数不属于同一个类或基类">63. 正在构造或析构的对象通过 . 或 -> 调用虚函数,而且该对象与当前构造或析构函数不属于同一个类或基类
- 64. typeid 作用于正在构造或析构的对象,而且该对象与当前构造或析构函数不属于同一个类或基类">64. typeid 作用于正在构造或析构的对象,而且该对象与当前构造或析构函数不属于同一个类或基类
- 65. dynamic_cast 作用于正在构造或析构的对象,而且该对象与当前构造或析构函数不属于同一个类或基类">65. dynamic_cast 作用于正在构造或析构的对象,而且该对象与当前构造或析构函数不属于同一个类或基类
- 66. 对模板函数进行非良构调用,或在模板定义和实例化上下文之外有更好的候选函数匹配">66. 对模板函数进行非良构调用,或在模板定义和实例化上下文之外有更好的候选函数匹配
- 67. 需要无限递归的模版实例化">67. 需要无限递归的模版实例化
- 68. 构造或析构函数在 function-try-block 的 handler 中访问非静态成员">68. 构造或析构函数在 function-try-block 的 handler 中访问非静态成员
- 69. 有返回值的函数在 function-try-block 的 handler 中没有正确返回">69. 有返回值的函数在 function-try-block 的 handler 中没有正确返回
- 70. 在 #if、 #elif 的条件中,由宏展开产生了 defined 表达式,或 defined 表达式格式不正确">70. 在 #if、 #elif 的条件中,由宏展开产生了 defined 表达式,或 defined 表达式格式不正确
- 71. #include 指令经宏展开后不满足标准格式">71. #include 指令经宏展开后不满足标准格式
- 72. 宏的实参列表中出现预处理指令">72. 宏的实参列表中出现预处理指令
- 73. 预处理运算符 # 的结果不是有效的字符串">73. 预处理运算符 # 的结果不是有效的字符串
- 74. 预处理运算符 ## 的结果不是有效的符号">74. 预处理运算符 ## 的结果不是有效的符号
- 75. #line 指定的行号为 0 或大于规定值">75. #line 指定的行号为 0 或大于规定值
- 76. #line 指令不符合标准格式">76. #line 指令不符合标准格式
- 77. 用 #define 定义或用 #undef 取消定义具有保留意义的名称">77. 用 #define 定义或用 #undef 取消定义具有保留意义的名称
- 78. 供语言机制调用的函数不符合要求">78. 供语言机制调用的函数不符合要求
- 79. 程序实现了应由标准库提供的功能">79. 程序实现了应由标准库提供的功能
- 80. 未经许可,向 std 命名空间添加声明或定义">80. 未经许可,向 std 命名空间添加声明或定义
- 81. 对标准库,特化模板类成员函数,特化模板类成员模板函数,特化、偏特化成员类模版">81. 对标准库,特化模板类成员函数,特化模板类成员模板函数,特化、偏特化成员类模版
- 82. 未经许可,向 posix 命名空间添加声明或定义">82. 未经许可,向 posix 命名空间添加声明或定义
- 83. 声明或定义标准库保留名称">83. 声明或定义标准库保留名称
- 84. 编译器未提供标准头文件,但编译时引入了非标准同名头文件">84. 编译器未提供标准头文件,但编译时引入了非标准同名头文件
- 85. 为标准库函数提供不符合要求的参数">85. 为标准库函数提供不符合要求的参数
- 86. 多线程调用标准库函数造成数据竞争">86. 多线程调用标准库函数造成数据竞争
- 87. 违反标准库函数要求的前置条件,除非标准库函数声明了这种情况会抛出异常">87. 违反标准库函数要求的前置条件,除非标准库函数声明了这种情况会抛出异常
- 88. offsetof 用于非 standard layout 类型,或用于计算静态成员以及成员函数的偏移量">88. offsetof 用于非 standard layout 类型,或用于计算静态成员以及成员函数的偏移量
- 89. 可变参数列表中省略号的前一个形式参数为引用、数组、函数,或具有与默认参数提升后不兼容的类型">89. 可变参数列表中省略号的前一个形式参数为引用、数组、函数,或具有与默认参数提升后不兼容的类型
- 90. longjmp 跳转使应被执行的析构函数未被执行">90. longjmp 跳转使应被执行的析构函数未被执行
附录:C++ 未定义行为成因列表 
未定义的行为(Undefined Behavior),指程序不可预测的执行效果,一般由错误的代码实现引起。出于效率、兼容性等多方面原因,语言标准不便于定义错误程序的明确行为,而是将其统称为“未定义”的行为,可以是崩溃,也可以是非预期的任意表现,有些问题在编译器和执行环境的特殊处理下也可能不会造成实质性的危害,但不具备可移植性。代码质量管理的一个重要目标就是消除会导致未定义行为的代码。
C++ 标准声明了导致未定义行为的情况,本文梳理了 ISO/IEC 14882:2003 和 ISO/IEC 14882:2011 前 18 章的相关内容,后 12 章的主题为标准库实现,相关内容的主旨在前 18 章中已有体现,C 语言相关内容可参见《C 未定义行为成因列表》。
1. 代码行以反斜杠结尾,且与下一行连接后生成通用字符名称
如果代码行以反斜杠结尾,预处理器会删除行尾的反斜杠和换行符,使其与下一行连接,如果连接后产生了以 \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
规则
2. 非空源文件未以换行符结尾,或以换行符结尾但换行符之前是反斜杠
示例:
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
3. 连接预处理符号时产生了通用字符名称
预处理运算符 ## 可以将两个符号连接成一个符号,如果产生了以 \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
4. 存在不符合词法的单引号或双引号
预处理器连接以反斜杠结尾的各行代码后将其转为预处理符号序列,在处理指令和展开宏之前,如果出现了不符合词法的单引号或双引号,会导致未定义的行为。
示例:
#defined X ' // Undefined behavior
#defined Y " // Undefined behavior
例中的引号无法与其他字符组成预处理符号,可能不会通过编译,也可能产生非预期的结果。
依据
ISO/IEC 14882:2003 2.4(2)-undefined
ISO/IEC 14882:2011 2.5(2)-undefined
5. 在 #include 指令中,’、”、\、//、/* 出现在定界符 < > 之间,或 ‘、\、//、/* 出现在定界符 “ 之间
示例:
#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
6. 无后缀 10 进制整数字面常量超过 long int 取值范围
示例:
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
7. 使用非标准转义字符
标准转义字符:
\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
规则
8. 修改字符串字面常量
示例:
*((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
规则
9. 窄字符串与宽字符串连接
示例:
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
规则
10. 违反 One Definition Rule
任何翻译单元不得包含变量、函数、类型、枚举或模板的多个定义,即 One Definition Rule,违反此规则会导致未定义的行为。
示例:
// In a.cpp
struct T {
int i;
};
// In b.cpp
struct T {
long i; // Undefined behavior, violates One Definition Rule
};
依据
ISO/IEC 14882:2003 3.2(5)-undefined
ISO/IEC 14882:2011 3.2(5)-undefined
11. 具有静态或线程存储期的对象在析构函数中调用 std::exit 函数
程序调用 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
规则
12. 函数内具有静态或线程存储期的对象已析构,之后该函数又被调用并引用到已析构的对象
示例:
// 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
13. 在对象析构之后使用对象
示例:
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
规则
14. 指针指向长度为 0 的内存空间并被解引用
示例:
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
规则
15. 内存回收函数抛出异常
示例:
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
规则
16. 使用不匹配的方法分配回收资源
示例:
T* p = new T;
....
free(p); // Undefined behavior
例中用 free 释放由 new 分配的内存空间,会导致未定义的行为。
依据
ISO/IEC 14882:2011 3.7.4.2(3)-undefined
规则
17. 使用已被释放的指针
示例:
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
18. 对象生命周期已结束,但未调用其有副作用的析构函数
示例:
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
19. 在分配空间后,生命周期开始前,或在生命周期结束后,回收空间前,通过指针访问对象
在对象的生命周期之外,通过指针进行如下操作会导致未定义的行为:
- 访问非静态成员函数
- 将指针转为基类指针
- 用 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
20. 在分配空间后,生命周期开始前,或在生命周期结束后,回收空间前,通过 glvalue 访问对象
在对象的生命周期之外,通过 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
21. 具有静态、线程或自动存储期和 non-trivial 析构函数的对象,其空间被非兼容类型的对象占据
示例:
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
22. 常量对象的空间或曾属于常量对象的空间被其他对象占据
示例:
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
23. 通过 glvalue 访问对象,但 glvalue 的类型不符合要求
只应通过以下类型的 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
规则
24. 通过 glvalue 引用不相关类型的对象或未初始化的对象
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
25. 浮点类型转换产生的结果无法在相应的空间中表示
示例:
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
规则
26. 浮点类型转为整数类型时,整数类型无法存储浮点类型的整数部分
示例:
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
规则
27. 整数类型转为浮点类型时,浮点类型无法存储整数的值
示例:
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
规则
28. 表达式求值依赖无确定顺序的副作用
示例:
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
29. 表达式的结果在数学上没有定义
如除 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
30. 被调用函数的语言链接性与该函数定义的语言链接性不符
示例:
// 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
31. 将非 POD 对象传入可变参数列表
可变参数列表是 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
32. 用 static_cast 将基类引用转为派生类引用,基类为虚基类,或引用的实际对象并非派生类对象
示例:
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
规则
33. 用 static_cast 将基类指针转为派生类指针,基类为虚基类,或指向的实际对象并非派生类对象
示例:
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
规则
34. 用 static_cast 将成员指针转为基类成员指针时,基类中没有相关成员
示例:
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
规则
35. 函数指针被转为不兼容的类型并执行
示例:
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
规则
36. 类型转换时去掉对象 const 属性并修改对象
示例:
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
规则
37. 对不完整类型的对象取地址,但该对象的完整类型重载了 operator &
示例:
struct T; // Incomplete type
T* foo(T& obj) {
return &obj; // Undefined behavior
}
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
规则
38. new 运算符第一个数组维度的参数为负数
示例:
int* foo(int n) {
return new int[n]; // Undefined in C++03 if ‘n’ is negative
}
如果 n 为负数,程序的行为在 C++03 中是未定义的,C++11 去掉了这项未定义行为的声明。
依据
ISO/IEC 14882:2003 5.3.4(6)-undefined
39. 用 delete 释放数组漏写中括号,用 delete 释放对象多写中括号,用 delete 释放非 new 表达式的结果
示例:
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
40. 被 delete 释放的对象或数组类型不符合要求
用 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
41. 用 delete 释放不完整类型的对象,但在对象完整类型声明中有 non-trivial 析构函数
示例:
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
规则
42. 对象解引用成员指针时,对象的动态类型不包含成员指针引用的成员
示例:
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
43. 对象解引用成员指针时,成员指针为空指针
示例:
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
44. / 或 % 运算符第二个操作数的值为 0
示例:
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
规则
45. 指针加减整数,结果超出了指针所在数组的地址范围
示例:
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
46. 不在同一数组中的指针相减
示例:
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
规则
47. 移位运算符右操作数为负数或超过相关类型比特位的数量
示例:
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
规则
48. 对有符号整数进行超出取值范围的左移运算
将符号位移出相关比特位,或将非符号位移入符号位,在数学上是没有意义的。
示例:
int b = -1 << 1; // Undefined
编译器可能会用乘法或除法实现有符号整数的移位,但不属于标准行为。
依据
ISO/IEC 14882:2011 5.8(2)-undefined
规则
49. 将对象赋值给具有重叠区域的对象
示例:
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
50. 函数没有通过 return 语句返回明确的值,但函数返回值被使用
示例:
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
规则
51. 递归地定义和初始化静态对象
示例:
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
52. 修改非 mutable 常量对象
示例:
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
规则
53. 使用没有 volatile 限定的 glvalue 访问有 volatile 限定的对象
示例:
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
规则
54. 具有 noreturn 属性的函数返回至调用方
示例:
[[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
规则
55. 空指针解引用
空指针表示没有指向任何对象的指针,通过空指针访问对象属于逻辑错误。
示例:
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
56. 对象的实际类型与当前静态类型不相关,并调用其非静态成员函数
示例:
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
57. 在构造函数或析构函数中调用纯虚函数
示例:
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_virtualCallInDestructor
58. 对象的实际类型与当前静态类型不相关,并调用其析构函数
示例:
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
59. 在对象生命周期结束后调用其析构函数
示例:
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
规则
60. 基类对象构造完毕之前调用成员函数
示例:
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
规则
61. 对成员或基类对象的不合理引用
对具有 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
62. 将对象指针转为其基类对象的指针时,基类对象尚未开始构造或已结束析构
示例:
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
63. 正在构造或析构的对象通过 . 或 -> 调用虚函数,而且该对象与当前构造或析构函数不属于同一个类或基类
示例:
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
64. typeid 作用于正在构造或析构的对象,而且该对象与当前构造或析构函数不属于同一个类或基类
示例:
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
65. dynamic_cast 作用于正在构造或析构的对象,而且该对象与当前构造或析构函数不属于同一个类或基类
示例:
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
66. 对模板函数进行非良构调用,或在模板定义和实例化上下文之外有更好的候选函数匹配
示例:
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 behavior, 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
67. 需要无限递归的模版实例化
示例:
template <class T>
class A
{
A<T>* p; // OK
A<T*> a; // Undefined behavior
};
如果实例化 A,需要实例化 A
依据
ISO/IEC 14882:2003 14.7.1(14)-undefined
ISO/IEC 14882:2011 14.7.1(15)-undefined
68. 构造或析构函数在 function-try-block 的 handler 中访问非静态成员
示例:
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
规则
69. 有返回值的函数在 function-try-block 的 handler 中没有正确返回
示例:
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
规则
70. 在 #if、 #elif 的条件中,由宏展开产生了 defined 表达式,或 defined 表达式格式不正确
示例:
#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
71. #include 指令经宏展开后不满足标准格式
示例:
#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
规则
72. 宏的实参列表中出现预处理指令
示例:
#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
73. 预处理运算符 # 的结果不是有效的字符串
示例:
#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
规则
74. 预处理运算符 ## 的结果不是有效的符号
示例:
#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
规则
75. #line 指定的行号为 0 或大于规定值
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
规则
76. #line 指令不符合标准格式
#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
规则
77. 用 #define 定义或用 #undef 取消定义具有保留意义的名称
如 __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
78. 供语言机制调用的函数不符合要求
如 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
79. 程序实现了应由标准库提供的功能
示例:
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
80. 未经许可,向 std 命名空间添加声明或定义
示例:
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
规则
81. 对标准库,特化模板类成员函数,特化模板类成员模板函数,特化、偏特化成员类模版
示例:
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
82. 未经许可,向 posix 命名空间添加声明或定义
示例:
namespace posix { // Undefined behavior
....
}
posix 命名空间是 C++ 的保留命名空间,自定义代码中不应出现名为 posix 的命名空间。
依据
ISO/IEC 14882:2011 17.6.4.2.2(1)-undefined
83. 声明或定义标准库保留名称
示例:
#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
84. 编译器未提供标准头文件,但编译时引入了非标准同名头文件
应选用健全的编译器,并保证编译环境的安全,否则程序的行为是未定义的。
依据
ISO/IEC 14882:2003 17.4.3.2(1)-undefined
ISO/IEC 14882:2011 17.6.4.4(1)-undefined
85. 为标准库函数提供不符合要求的参数
依据
ISO/IEC 14882:2003 17.4.3.7(1)-undefined
ISO/IEC 14882:2011 17.6.4.9(1)-undefined
86. 多线程调用标准库函数造成数据竞争
某些库函数与共享数据相关,但并未提供安全的同步机制,在多线程环境中使用会导致未定义的行为。
示例:
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
规则
87. 违反标准库函数要求的前置条件,除非标准库函数声明了这种情况会抛出异常
示例:
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
88. offsetof 用于非 standard layout 类型,或用于计算静态成员以及成员函数的偏移量
示例:
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
规则
89. 可变参数列表中省略号的前一个形式参数为引用、数组、函数,或具有与默认参数提升后不兼容的类型
示例:
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
90. longjmp 跳转使应被执行的析构函数未被执行
示例:
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
规则