1 野指针
1.1 野指针危害
- 指针变量中的值是非法的内存地址,进而形成野指针
- 野指针不是
NULL
指针,是指向不可用内存地址的指针 NULL
指针并无危害,很好判断,也很好调试- C 语言中无法判断一个指针所保存的地址是否合法
1.2 野指针的由来
-
局部指针变量没有被初始化
-
指针所指向的变量在指针之前被销毁:返回局部数组
-
使用已经释放过的指针:指针
free
后 -
进行了错误的指针运算
-
进行了错误的强制类型转换:整型值当作指针值
-
示例:野指针
-
Demo
#include <stdio.h> #include <malloc.h> int main() { int* p1 = (int*)malloc(40); //p1指向malloc申请的动态内存 int* p2 = (int*)1234567; //p2指向一个整型值强制类型转换而来的地址,极大可能是野指针。但是程序不会在此处崩溃,取决于如何使用p2 int i = 0; printf("%p ",p1); for(i = 0; i < 40; i++) { *(p1 + i) = 40 - i; //野指针:进行了错误的指针运算(40个字节内存当作了40个int字节内存的),改写了非法的内存地址。 } free(p1); //p1所指向的内存释放后,p1保存的值还是原先的地址值,需要重新赋值为NULL printf("%p ",p1); for(i = 0; i < 40; i++) { p1[i] = p2[i]; //野指针使用:使用已经释放了的内存空间,非常难以调试,因为不会立即产生影响 } return 0; }
-
编译运行
0x9baa008 0x9baa008 段错误
-
修改
#include <stdio.h> #include <malloc.h> int arr[40] = {1,2,3,4,5,6,7}; int main() { int* p1 = (int*)malloc(40*sizeof(int)); int* p2 = arr; int i = 0; printf("%p ",p1); for(i = 0; i < 40; i++) { *(p1 + i) = 40 - i; } free(p1); p1 = NULL; printf("%p ",p1); for(i = 0; i < 40; i++) { p1[i] = p2[i]; //这里编译运行就会报错 } return 0; }
-
编译运行
0x8ea8008 (nil) 段错误
-
1.3 避免野指针的基本原则
-
绝不返回局部变量和局部数组的地址
-
任何变量在定义后必须 0 初始化
-
字符数组必须确认 0 结束符后才能成为字符串
-
任何使用与内存操作相关的函数必须指定长度信息
-
示例:无处不在的野指针
-
Demo
#include <stdio.h> #include <string.h> #include <malloc.h> struct Student { char* name; int number; }; char* func() { char p[] = "ABCD"; return p; //返回局部字符数组的地址 } void del(char* p) { printf("%s ", p); free(p); } int main() { struct Student s; //没有进行初始化,s.name为野指针 char* p = func(); //产生野指针,p为野指针 strcpy(s.name, p); //使用野指针p,s.name s.number = 99; p = (char*)malloc(5); strcpy(p, "ABCDEFGHI"); //没有指定长度信息,产生内存越界,操作了野指针指向的内存空间 del(p); return 0; }
-
编译
test.c: In function 'func': test.c:15: warning: function returns address of local variable
-
运行
段错误
-
2 常见内存错误
-
结构体成员指针未初始化
-
结构体成员指针未分配足够的内存
-
内存分配成功,但并未初始化
-
内存操作越界
-
示例:常见的内存错误
-
Demo1
#include <stdio.h> #include <malloc.h> void test(int* p, int size) { int i = 0; for(i = 0; i < size; i++) { printf("%d ", p[i]); } free(p); //多次释放p指针所指向的内存空间 } void func(unsigned int size) { int* p = (int*)malloc(size * sizeof(int)); int i = 0; //如果size为奇数,p指针所指向的堆空间不会释放 if( size % 2 != 0 ) { return; } for(i = 0; i < size; i++) { p[i] = i; printf("%d ", p[i]); } free(p); } int main() { //在哪个函数中所申请的内存,就在哪个函数中进行释放 int* p = (int*)malloc(5 * sizeof(int)); test(p, 5); free(p); func(9); func(10); return 0; }
-
编译运行:程序崩溃
-
Demo2
#include <stdio.h> #include <malloc.h> struct Demo { char* p; }; int main() { struct Demo d1; //结构体变量未进行初始化 struct Demo d2; //结构体变量未进行初始化 char i = 0; for(i = 'a'; i < 'z'; i++) { d1.p[i] = 0; //d1.p指向一个随机的地址,是一个野指针,这里操作了野指针 } d2.p = (char*)calloc(5, sizeof(char)); //申请的内存空间全部置零,产生段错误 printf("%s ", d2.p); //d2.p指向一个空串 for(i = 'a'; i < 'z'; i++) { d2.p[i] = i; //d2.p所指向的内存空间只有5个字节大小,不能容纳下26个字节,内存越界(本质是指针运算产生野指针),不会立即产生影响 } free(d2.p); return 0; }
-
编译运行
段错误
-
3 内存操作的规则
-
动态内存申请之后,应该立即检查指针值是否为
NULL
,防止使用NULL
指针int* p = (int*)malloc(56); if(p != NULL) { //Do something here! } free(p);
-
free
指针之后必须立即赋值为NULL
int* p = (int*)malloc(20); free(p); p = NULL; //... if(p != NULL) { //Do something here! }
-
任何与内存操作相关的函数都必须带长度信息
void print(int* p,int size) { int i = 0; char buf[128] = {0}; snprintf(buf,sizeof(buf),"%s","ABCDEFGH"); for(i = 0;i < size; i++) { printf("%d ",p[i]); } }
-
malloc
操作和free
操作必须匹配,防止内存泄漏和多次释放- 当
malloc
次数多于free
时,产生内存泄漏 - 当
malloc
次数少于free
时,程序可能崩溃
void func() { int* p = (int*)malloc(20); free(p); } int main() { int* p = (int*)malloc(40); func(); free(p); return 0; }
- 当