C ++程序员应该知道哪些常见的未定义行为?
像这样说:
a[i] = i++;
C ++程序员应该知道哪些常见的未定义行为?
像这样说:
a[i] = i++;
Answers:
NULL
指针memcpy
复制重叠的缓冲区。int64_t i = 1; i <<= 72
,未定义)int i; i++; cout << i;
)volatile
或sig_atomic_t
接收信号时类型的任何对象的值long int
#if
表达式中动态生成定义的标记函数参数的评估顺序是未指定的行为。(与未定义的行为不同,这不会使您的程序崩溃,爆炸或订购比萨饼。)
唯一的要求是在调用函数之前必须对所有参数进行全面评估。
这个:
// 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.
我最喜欢的是“模板实例化中的无限递归”,因为我相信它是唯一在编译时发生未定义行为的实例。
除了未定义的行为外,还存在同样讨厌的实现定义的行为。
当程序执行某项结果(标准未指定)时,会发生未定义的行为。
实现定义的行为是程序的一种动作,其结果不是标准定义的,但是实现需要记录下来。堆栈溢出问题中的一个示例是“多字节字符文字”,是否有C编译器无法对此进行编译?。
实现定义的行为只会在您开始移植时咬住您(但是也要移植到新版本的编译器!)
变量只能在表达式中更新一次(技术上在序列点之间一次)。
int i =1;
i = ++i;
// Undefined. Assignment to 'i' twice in the same expression.
基本了解各种环境限制。完整列表在C规范的5.2.4.1节中。这里有一些;
实际上,对于switch语句的1023个case标签的限制我感到有些惊讶,我可以预见到,生成的代码/ lex /解析器相当容易被超过。
如果超出这些限制,则您将具有不确定的行为(崩溃,安全漏洞等)。
是的,我知道这来自C规范,但是C ++共享这些基本支持。
使用memcpy
重叠的内存区域之间进行复制。例如:
char a[256] = {};
memcpy(a, a, sizeof(a));
根据C标准(包含在C ++ 03标准中),行为是未定义的。
概要
1 / #include无效* memcpy(无效*限制s1,常量无效*限制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函数将s2指向的对象中的n个字符复制到s1指向的对象中。就像是先将s2所指向的对象中的n个字符复制到不与s1和s2所指向的对象重叠的n个字符的临时数组中,然后再将临时数组中的n个字符复制到s1指向的对象。退货
3 memmove函数返回s1的值。
C ++保证大小的唯一类型是char
。大小为1。所有其他类型的大小取决于平台。