一、对NULL指针的解引用操作
代码一 动态内存申请后未进行空指针检查
//err void test() { int *p = (int *)malloc(INT_MAX/4); *p = 20; //未进行空指针判断,若p的值是NULL,就会有问题 free(p); } //correct void test() { int *p = (int *)malloc(INT_MAX/4); if(p == NULL) //进行返回值NULL指针检查,更为严谨 { exit(-1); } else { *p = 20; } free(p); }
在使用动态内存函数(malloc、calloc、reallo)申请空间后,需要进行空指针判断。因为动态内存函数申请空间有可能会失败,若开辟失败,则函数会返回一个NULL指针。因此对动态内存函数的返回值,一定要做检查。
注意,free函数是允许传入NULL指针的。当free的参数为NULL指针时,函数什么事情也不做,不会出现动态内存错误。
代码二 指针变量的传值调用
*运行Test函数,结果如何?会打印出 hello world 吗?
//运行Test函数,结果如何? void GetMemory(char *p) { p = (char *)malloc(100); } void Test(void) { char *str = NULL; GetMemory(str); strcpy(str, "hello world"); printf(str); }
答案是并不会。该程序无法正常输出,原因在于其中也有“NULL指针陷阱”。
如下图所示:
在Test函数中创建指针变量 *str,并赋值为NULL。
在Test函数中调用GetMemory函数。GetMemory函数的形参为char *p,形参是实参的临时拷贝,形参的改变不影响实参。*p与*str是两个不同的变量,存储在栈上两块完全不同的内存空间。
GetMemory函数中,变量p指向了一块新开辟的内存空间,但p的改变并不影响实参*str。
由于局部变量建立在栈上,函数调用结束后,生命周期就结束,因此在GetMemory函数结束后,该函数的栈帧销毁,变量*p也随之销毁。而此时*str的值并未改变,仍然是NULL。
因此最后在Test函数中strcpy试图将字符串"hello world"拷贝到NULL指针str中,strcpy函数出错。该代码无法正常运行。
以上代码可以理解为指针的传值调用。我们知道,要在调用的函数内“远程”改变函数外的变量值,需要传入变量的地址。因此,要让上面的代码能正常打印"hello world",只需在函数中传入指针变量的地址——二级指针即可。(当然,通过返回值的方式,在函数调用后接收返回值,也是可行的。)
//correct void GetMemory(char* *p) //传入二级指针 { *p = (char *)malloc(100); //对二级指针解引用,获得指针变量的地址,从而改变指针变量的值 } void Test(void) { char *str = NULL; GetMemory(str); strcpy(str, "hello world"); printf(str); free(str); }
二、对动态开辟空间的越界访问
void test() { int i = 0; int *p = (int *)malloc(10*sizeof(int)); if(NULL == p) { exit(EXIT_FAILURE); } for(i=0; i<=10; i++) { *(p+i) = i;//当i是10的时候越界访问 } free(p); }
该程序中,指针p指向一片开辟了40个字节(即10个整型)的存储空间。若要访问该空间的内存值,则0到9才是合法的下标数值。然而在遍历赋值时,i越界到了10.这时,由于访问了无权限访问的内存空间,程序就会崩溃,无法正常运行。
三、对非动态开辟内存使用free释放
1.void test() { int a = 10; int *p = &a; free(p); }
以上程序中,a变量并不是动态内存开辟的。令指针p指向a的内存空间并free(p),编译器会运行出错(Debug Assertion failed)。注意:free只能释放动态开辟的内存空间。
四、使用free释放一块动态开辟内存的一部分
void test() { int *p = (int *)malloc(100); p++; free(p); //p不再指向动态内存的起始位置 }
以上程序中,原本指向动态内存开辟空间的p后来被移动,最终所指向的位置不再是开辟空间的起始位置。这时再将p释放,释放的只是部分动态开辟内存而不是全部,这样程序仍然会在运行时崩溃。
因此要注意:在动态开辟空间之后,如果要遍历开辟的内存空间,不要用开辟时接收的指针来遍历。应当再定义一个别的指针如指针q,进行运算操作。将p指针不动留到最后,用于最终的释放操作。
五、对同一块动态内存多次释放
void test() { int *p = (int *)malloc(100); free(p); free(p); //重复释放 }
该程序仍然会出现运行错误。在第一次free(p)后,p所指向的空间就已经被释放。此时的p相当于一个野指针,指向已经被释放了的无权限访问的内存空间。
而之后再进行一次free(p),p所指的内容已经不是动态内存开辟的空间了。因此程序会出错而中止。
注意,若在第一次free(p)后,将p = NULL,然后再进行第二次free(p),此时不会发生任何错误。因此,在释放完动态开辟的内存后,要记得将指针赋值为NULL,使程序更加严谨,避免一时疏忽从而让程序出错。
常见的动态内存错误总结(二)
+https://developer.aliyun.com/article/1519319?spm=a2c6h.13262185.profile.54.71c436831FgmJg