C++程序员应该知道的所有常见的未定义行为是什么?
说,像:
a[i] = i++;
取消引用NULL
指针
取消引用由大小为零的"新"分配返回的指针
使用指向生命周期已结束的对象的指针(例如,堆栈已分配的对象或已删除的对象)
取消引用尚未明确初始化的指针
执行指针运算,在数组的边界(上方或下方)之外产生结果.
在超出数组末尾的位置取消引用指针.
将指针转换为不兼容类型的对象
使用memcpy
复制重叠的缓冲区.
以负数或超出该对象大小的偏移量读取或写入对象或数组(堆栈/堆溢出)
有符号整数溢出
评估未在数学上定义的表达式
左移值为负数(向右移位负值是实现定义的)
将值移动大于或等于数字中的位数(例如int64_t i = 1; i <<= 72
,未定义)
将数值转换为无法由目标类型表示的值(直接或通过static_cast)
在明确分配之前使用自动变量(例如int i; i++; cout << i;
)
使用比其他类型的任何对象的值volatile
或者sig_atomic_t
在收到的信号的
尝试在其生命周期内修改字符串文字或任何其他const对象
在预处理期间将窄字符串与宽字符串文字连接起来
不从值返回函数返回值(直接或通过从try块中流出)
同一实体的多个不同定义(类,模板,枚举,内联函数,静态成员函数等)
模板实例化中的无限递归
使用不同的参数调用函数或链接到函数定义为使用的参数和链接.
具有静态存储持续时间的对象的级联破坏
分配给部分重叠的对象的结果
在其静态对象的初始化期间递归地重新进入函数
从构造函数或析构函数中对对象的纯虚函数进行虚函数调用
指未经建造或已被破坏的物体的非静止构件
非空源文件,不以换行符结尾,或以反斜杠结尾(在C++ 11之前)
反斜杠后跟一个字符,该字符不是字符或字符串常量中指定的转义码的一部分(这是在C++ 11中实现定义的).
超出实现限制(嵌套块的数量,程序中的函数数量,可用的堆栈空间......)
预处理器数值,不能用a表示 long int
类似函数的宏定义左侧的预处理指令
在#if
表达式中动态生成已定义的标记
在销毁具有静态存储持续时间的程序期间调用exit
评估函数参数的顺序是未指定的行为.(这不会使您的程序崩溃,爆炸或订购披萨......与未定义的行为不同.)
唯一的要求是在调用函数之前必须完全评估所有参数.
这个:
// The simple obvious one. callFunc(getA(),getB());
可以相当于:
int a = getA(); int b = getB(); callFunc(a,b);
或这个:
int b = getB(); int a = getA(); callFunc(a,b);
它可以是; 这取决于编译器.结果可能很重要,具体取决于副作用.
编译器可以自由地重新排序表达式的求值部分(假设含义不变).
从原来的问题:
a[i] = i++; // This expression has three parts: (a) a[i] (b) i++ (c) Assign (b) to (a) // (c) is guaranteed to happen after (a) and (b) // But (a) and (b) can be done in either order. // See n2521 Section 5.17 // (b) increments i but returns the original value. // See n2521 Section 5.2.6 // Thus this expression can be written as: int rhs = i++; int lhs& = a[i]; lhs = rhs; // or int lhs& = a[i]; int rhs = i++; lhs = rhs;
双重检查锁定.一个容易犯的错误.
A* a = new A("plop"); // Looks simple enough. // But this can be split into three parts. (a) allocate Memory (b) Call constructor (c) Assign value to 'a' // No problem here: // The compiler is allowed to do this: (a) allocate Memory (c) Assign value to 'a' (b) Call constructor. // This is because the whole thing is between two sequence points. // So what is the big deal. // Simple Double checked lock. (I know there are many other problems with this). if (a == null) // (Point B) { Lock lock(mutex); if (a == null) { a = new A("Plop"); // (Point A). } } a->doStuff(); // Think of this situation. // Thread 1: Reaches point A. Executes (a)(c) // Thread 1: Is about to do (b) and gets unscheduled. // Thread 2: Reaches point B. It can now skip the if block // Remember (c) has been done thus 'a' is not NULL. // But the memory has not been initialized. // Thread 2 now executes doStuff() on an uninitialized variable. // The solution to this problem is to move the assignment of 'a' // To the other side of the sequence point. if (a == null) // (Point B) { Lock lock(mutex); if (a == null) { A* tmp = new A("Plop"); // (Point A). a = tmp; } } a->doStuff(); // Of course there are still other problems because of C++ support for // threads. But hopefully these are addresses in the next standard.
剥离后const
使用const_cast<>
以下方式分配常量:
const int i = 10; int *p = const_cast( &i ); *p = 1234; //Undefined
我最喜欢的是"模板实例化中的无限递归"因为我相信它是唯一一个在编译时发生未定义行为的人.
除了未定义的行为,还有同样讨厌的实现定义的行为.
当程序执行标准未指定结果的事情时,会发生未定义的行为.
实现定义的行为是程序的一个动作,其结果不是由标准定义的,而是需要实现的文档.一个例子是"多字节字符文字",来自Stack Overflow问题是否有一个C编译器无法编译它?.
实现定义的行为只会在您开始移植时咬你(但升级到新版本的编译器也在移植!)