一个C++程序员应该知道的常见未定义行为有哪些?
例如:
a[i] = i++;
一个C++程序员应该知道的常见未定义行为有哪些?
例如:
a[i] = i++;
NULL
指针进行解引用memcpy
复制重叠缓冲区int64_t i = 1; i <<= 72
是未定义的)int i; i++; cout << i;
)volatile
或sig_atomic_t
类型的对象的值long int
表示#if
表达式中动态生成定义的标记sizeof(int) * CHAR_BIT <= 72
为真时,才是未定义行为。您放置37 << 72
结果的对象是无关紧要的,表达式本身就会调用未定义的行为。例如,uint8_t n = (1 << 15);
是完全可以的。首先评估1 << 15
,其为2 ** 15。然后将结果从“int”隐式转换为“uint8_t”,这意味着n == 0
(由于模算术)。 - David Stone函数参数的求值顺序是未指定的行为。(这不会使您的程序崩溃,爆炸或订购披萨……与未定义行为不同。)
唯一的要求是在调用函数之前必须完全对所有参数进行求值。
这个:
// 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.
我最喜欢的是"Infinite recursion in the instantiation of templates",因为我认为这是唯一一个在编译时发生未定义行为的例子。
使用const_cast<>
去除常量属性后将值赋给常量:
const int i = 10;
int *p = const_cast<int*>( &i );
*p = 1234; //Undefined
在表达式中,变量只能更新一次(在技术上,在序列点之间只能更新一次)。
int i =1;
i = ++i;
// Undefined. Assignment to 'i' twice in the same expression.
使用memcpy
函数在重叠的内存区域之间进行复制。例如:
char a[256] = {};
memcpy(a, a, sizeof(a));
根据C标准,这种行为是未定义的,这个标准被C++03标准所包含。
概要
1/ #include void *memcpy(void * restrict s1, const void * restrict s2, size_t n);
描述
2/ memcpy函数将n个字符从s2指向的对象复制到s1指向的对象中。如果在重叠的对象之间进行复制,则行为是未定义的。返回值:3 memcpy函数返回s1的值。
概要
1 #include void *memmove(void *s1, const void *s2, size_t n);
描述
2 memmove函数将n个字符从s2指向的对象复制到s1指向的对象中。复制的过程就好像首先将来自s2指向的对象的n个字符复制到一个不重叠于s1和s2指向的对象的临时数组中,然后再将来自临时数组的n个字符复制到s1指向的对象中。返回值:3 memmove函数返回s1的值。
不同编译单元中的命名空间级对象不应该彼此依赖进行初始化,因为它们的初始化顺序是未定义的。