C/C++代码错误总结(1)
今天看了一个公司产品的视频介绍,正好最近要总结C/C++项目中经常发现的代码错误,特写出来与大家分享。
1 FORWARD_NULL
通常发生的情况是,一个指针先被判断是否等于NULL,然后指针被非法引用。
非法引用NULL的指针会导致程序崩溃。程序员在判断指针是否等于NULL时,没能正确的处理好,或者是忘记了NULL在代码路径的情况。
举例说明:
int forward_null_example1(int *p) {
int x;
if( p == NULL ) {
x = 0;
} else {
x = *p;
}
x += fn();
*p = x; // ERROR: p is potentially NULL
return 0;
}
2 USE_AFTER_FREE
即使用已被释放的内存、同一指针被释放多次。
当内存被释放后,就不能再被安全的使用。而同一内存的多次释放通常会导致未定义的行为,包括内存冲突和程序崩溃。
引用已释放的指针也是非常危险的,因为指针的值不确定或者指针指向任意内存的位置。
在多线程的程序中,多次释放同一内存非常危险,因为已释放的内存可能已被另一线程申请,再次释放可能导致该线程的内存也被释放,在这种情况下,该线程使用已释放的内存,可能导致紊乱,同时很难被编译器跟踪。
举例说明:
void fun(int * p) {
free (p);
int k = *p; // p is dereferenced after it is freed.
}
int f(void *p) {
if(some_error()) {
free(p);
return -1;
}
return 0;
}
void g() {
void *p = malloc(42);
if(f(p) < 0) {
free(p); // double free of pointer "p"
// previously freed in call to "f".
}
use(p);
}
3 RESOURCE_LEAK
资源泄漏(内存泄漏)指变量在出了自己的作用范围后,占用的资源仍然驻守在内存里,没有被释放。
严重的内存泄漏能导致进程崩溃,即便是很小的内存泄漏,在系统长时间运行没有重启后,也会产生错误。如果内存泄漏是由用户输入或者网络数据触发,还会成为“拒绝服务攻击”的对象。
文件句柄或者网络套接字的泄漏会导致程序崩溃、拒绝服务攻击或者打开其他文件或套接字失败。操作系统通常会限制进程的文件句柄和套接字个数。当达到限制的最大值时,进程要申请新的资源时,首先要关闭一些已打开的资源。如果进程中存在资源泄漏,进程自己将没有办法回收这些资源,除非强行终止该进程。
多数情况下,这些泄漏通常发生在某个错误的路径,比如说,某个异常处理的分支。这种情况下,正确的做法应该是将程序跳转(GOTO)到该函数的出口,出口处应释放这些资源。
在C++中,RAII(the Resource Acquisition Is Initialization)机制能够自动的释放资源。RAII包括类的一个申请资源的构造函数和释放资源的析构函数。当一个类的局部变量声明后,当离开该变量的作用范围后,RAII会自动调用其析构函数释放资源,这同样也会保护throw异常导致的泄漏。
举例说明:
int leak_example(int c) {
void *p = malloc(10);
if(c)
return -1; // "p" is leaked
/* ... */
free(p);
return 0;
}
int wrong_error_check() {
void *p = malloc(10);
void *q = malloc(20);
if(!p || !q)
return -1; // "p" or "q" may be leaked if the other is NULL
/*...*/
free(q);
free(p);
return 0;
}
int test(int i) {
void *p = malloc(10);
void *q = malloc(4);
if(i > 0)
p = q; // p is overwritten and is the last pointer
else // to the allocated memory
free(q);
free(p);
return 0;
}
void test(int c) {
FILE *p = fopen("foo.c", "rb");
if(c)
return; // leaking file pointer "p"
fclose(p);
}
4 NULL_RETURN
函数的返回值可能是NULL,所以使用函数返回值的变量一定要首先检查是否是NULL,否则就可能出错。
程序员经常不会去检查函数的返回值,而是直接以危险的方式去使用,可能会由于对NULL的非法引用,导致程序崩溃。
举例:
void bad_malloc() {
// malloc returns NULL on error
struct some_struct *x = malloc(sizeof(*x), GFP_KERNEL);
// ERROR: memset dereferences x
memset(x, 0, sizeof(struct some_struct);
}
今儿先写这么多,以后再续。