在开发项目中,如果存在未定义行为的代码,整个程序的执行就会变得很诡异,之前在移动端开发的时候,就碰到UB后,Bugly的符号位出现了严重的偏移,只能通过分析代码来解决,耗费了很多的精力。
C++标准给出的未定义行为的解释:
未定义行为(undefined behavior,UB)——对程序的行为无任何限制。未定义行为的例子是数组边界外的内存访问,有符号整数溢出,空指针的解引用,在一个表达式中对同一标量多于一次的中间无序列点(C++11前)无序(C++11起)的修改,通过不同类型的指针访问对象,等等。编译器不需要诊断未定义行为(尽管许多简单情形确实会得到诊断),而且所编译的程序不需要做任何有意义的事
1.除零
int main()
{
int x = 25, y = 0;
int z = x / y;
return 0;
}
2.变量未初始化
std::size_t f(int x)
{
std::size_t a;
if(x) // either x nonzero or UB
a = 42;
return a;
}
3.访问空指针NULL
int main()
{
int *ptr = NULL;
printf("%d", *ptr);
return 0;
}
4.访问越界
int main()
{
int arr[5];
for (int i=0; i<=5; i++)
printf("%d ", arr[i]);
}
5.有符号数溢出
int main()
{
int x = INT_MAX;
printf("%d", x+1);
return 0;
}
6.试图更改字符串字面值
int main()
{
char *s = "geeksforgeeks";
s[0] = 'e';
return 0;
}
7.在一个序列操作值未确定的情况下对一个变量多次更改
#include <stdio.h>
int main()
{
int i = 8;
int p = i++*i++;
printf("%d\n", p);
}
8.解引用空指针
int foo(int* p) {
int x = *p;
if(!p) return x; // Either UB above or this branch is never taken
else return 0;
}
int bar() {
int* p = nullptr;
return *p; // Unconditional UB
}
9.在对象的生命周期结束后访问对象
同3,对象生命周期结束后,内存被释放。
10.有返回类型的函数没有从return结束
int test(int i)
{
if (i > 0) {
retrun i++;
}
}
当i<=0时,返回的是随机值
11.指针double free
void doDoubleFree() {
char *p = new char[16];
delete[] p;
delete[] p;
}
12.不要判断this为nullptr
int Class::member() {
if (this == nullptr) {
// removed by the compiler, it would be UB
// if this were ever null
return 42;
} else {
return 0;
}
}
13.不要判断对象的引用是nullptr
14.sprintf或者snprint
sprintf(buf, "%s text", buf);
调用导致在重叠对象之间发生复制
15.const_cast
const int v = 1;
const int* a = &v;
int* b = const_cast<int*>(a);
++(*b);
强制转换为非常量后修改其值
16.先释放然后重新分配
for (p = head; p != NULL; p = p->next) {
free((char*) p);
}
并非所有的内部实现在内存被释放后还能较长时间的保留
17.tls中pthread_getspecific
pthread_key_t pKey;
pthread_key_create(&pKey, nullptr);pthread_key_delete(pKey);
auto pValue = pthread_getspecific(pKey); /* 未定义行为*/
除了以上的UB类型外,在开发的过程中,使用到的API可能也会出现UB行为,C++的API文档也会有相关的说明,出现UB后,问题经常特别的隐蔽,查找耗费大量的精力和时间。
https://xueqing.github.io/blog/cplusplus/undefined_behavior