C ++程序员应该知道的所有常见的未定义行为是什么?
说,像:
a[i] = i++;
答案 0 :(得分:233)
NULL
指针memcpy
to copy overlapping buffers。int64_t i = 1; i <<= 72
未定义)int i; i++; cout << i;
)volatile
或sig_atomic_t
以外的任何类型对象的值long int
#if
表达式答案 1 :(得分:31)
评估函数参数的顺序是 未指定行为。 (这不会使您的程序崩溃,爆炸或订购披萨......与 未定义的行为不同。)
唯一的要求是在调用函数之前必须完全评估所有参数。
此:
// 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);
它可以是;这取决于编译器。结果可能很重要,取决于副作用。
答案 2 :(得分:27)
编译器可以自由地重新排序表达式的评估部分(假设含义不变)。
从最初的问题:
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.
答案 3 :(得分:5)
使用const
剥离const_cast<>
后分配常量:
const int i = 10;
int *p = const_cast<int*>( &i );
*p = 1234; //Undefined
答案 4 :(得分:5)
我最喜欢的是“模板实例化中的无限递归”,因为我相信它是编译时未定义行为发生的唯一一个。
答案 5 :(得分:5)
除了未定义的行为之外,还有同样令人讨厌的实现定义的行为。
当程序执行标准未指定结果的事情时,会发生未定义的行为。
实现定义的行为是程序的一个动作,其结果不是由标准定义的,而是需要实现的文档。一个例子是“多字节字符文字”,来自Stack Overflow问题 Is there a C compiler that fails to compile this? 。
实现定义的行为只会在您开始移植时咬你(但升级到新版本的编译器也在移植!)
答案 6 :(得分:4)
变量只能在表达式中更新一次(技术上一次在序列点之间)。
int i =1;
i = ++i;
// Undefined. Assignment to 'i' twice in the same expression.
答案 7 :(得分:3)
对各种环境限制的基本了解。完整列表在C规范的5.2.4.1节中。这里有几个;
我对切换语句的1023个案例标签的限制实际上感到有些惊讶,我可以预见到生成的代码/ lex /解析器的覆盖率相当容易。
如果超出这些限制,则会出现未定义的行为(崩溃,安全漏洞等)。
是的,我知道这是来自C规范,但C ++共享这些基本支持。
答案 8 :(得分:2)
C ++保证大小的唯一类型是char
。大小为1.所有其他类型的大小取决于平台。
答案 9 :(得分:2)
不同编译单元中的命名空间级对象不应该相互依赖进行初始化,因为它们的初始化顺序是未定义的。
答案 10 :(得分:2)
使用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功能 将s2指向的对象中的n个字符复制到对象中 s1指出。如果在重叠的对象之间进行复制, 行为未定义。返回3 memcpy函数返回 s1的值。
概要
1 #include void * memmove(void * s1,const void * s2,size_t N);
描述
2 memmove函数从指向的对象复制n个字符 通过s2进入s1指向的对象。复制就像发生一样 首先将s2指向的对象中的n个字符复制到a中 不与对象重叠的n个字符的临时数组 由s1和s2指向,然后是临时的n个字符 数组被复制到s1指向的对象中。返回
3 memmove函数返回s1的值。