相信经过我们上一篇博客对动态内存管理的了解,我们对这部分知识已经大体了解了。我们在完成关于动态内存存储的有关工程时,经常会出现代码错误的情况,一般包括以下几种,我们一起来分析一下
目录
1.对NULL指针的解引用操作
1.1错误代码
1.2正确代码
2.对动态开辟空间的越界访问
2.1错误代码
2.2正确代码
3.对非动态开辟的内存空间进行free释放
3.1错误代码
3.2正确代码
4.使用free释放一块动态开辟内存的一部分
4.1错误代码
4.2正确代码
5.对同一块动态内存多次释放
5.1错误代码
5.2正确代码
6.动态开辟内存空间忘记释放
6.1错误代码
6.2正确代码
7.动态开辟内存在释放前程序结束
7.1错误代码
7.2正确代码
在分析之前,我们首先要知道free函数和动态内存开辟函数必须成对使用
这是什么意思呢,就是未对我们开辟出来的动态空间进行判断是否开辟成功,就对指针进行了应用
#define _CRT_SECURE_NO_WARNINGS 1 #include
#include #include #include //对NULL指针解引用操作问题 int main() { int* p = (int*)malloc(sizeof(int)*400000000000); int i = 0; for (i = 0; i <10; i++) { *(p + i) = i; printf("%d ", *(p + i)); } free(p); p = NULL; return 0; } 我们在有关malloc函数的博客中说过,malloc函数开辟内存也不是百分百会成功,需要开辟的空间足够大它就会失败,如果malloc开辟内存失败,会返回一个空指针,但是这里没有对它进行判断就直接使用,p如果为空指针,函数的后续使用就有问题,代码不安全
我们运行发现,代码没有结果,我们存入内存的数也没有打印,此时malloc开辟内存失败
其实有两种解决办法
1.方法一:加上判断语句,判断malloc是否开辟内存成功,成功则继续执行,不成功,则结束代码
#define _CRT_SECURE_NO_WARNINGS 1 #include
#include #include #include //对NULL指针解引用操作问题 int main() { int* p = (int*)malloc(sizeof(int)*400000000000); //判断malloc函数是否开辟内存成功 if (p == NULL) { printf("%s\n", strerror(errno)); return 1; } int i = 0; for (i = 0; i <10; i++) { *(p + i) = i; printf("%d ", *(p + i)); } free(p); p = NULL; return 0; } 及时截断代码,告诉我们错误信息,malloc开辟皮内存不成功,没有足够的空间
2.方法二:使用断言assert函数,判断指针p是否为空指针,但assert只会告诉你它是空指针,不会打印原因
int main() { int* p = (int*)malloc(sizeof(int) * 400000000000); assert(p); int i = 0; for (i = 0; i < 10; i++) { *(p + i) = i; printf("%d ", *(p + i)); } free(p); p = NULL; return 0; }
意思就是说,你假如只开辟了10个整型的空间,但是你后续使用这块空间想使用20个,那么多使用的这10个就属于越界访问
int main() { int* p = (int*)malloc(100);//这里开辟了25个整型空间 if (p == NULL) { printf("%s\n", strerror(errno)); return 1; } int i = 0; for (i = 0; i < 200; i++) { *(p + i) = i; printf("%d ", *(p + i)); } free(p); p = NULL; return 0; }
我们只开辟了25个int型的空间,但是我们向后使用了200个整型空间,这就属于越界访问,这是违法的,虽然可以打印出我们要求的数据,但这是不安全的,在有些编译器里面会显示代码错误。
我们在VS编译器中,虽然有打印出结果,但是程序却一直不结束,这也足以代码存在问题
解决这个问题,只需要我们开辟出多少空间就使用多少空间,不多用
int main() { int* p = (int*)malloc(100);//这里开辟了25个整型空间 if (p == NULL) { printf("%s\n", strerror(errno)); return 1; } int i = 0; for (i = 0; i < 25; i++) { *(p + i) = i; printf("%d ", *(p + i)); } free(p); p = NULL; return 0; }
我们运行代码会发现,代码很流畅,不存在刚刚的卡顿
我们要知道,free释放空间只适用于动态开辟的内存空间,也就是堆区的,不能释放存放在栈区的空间
//对非动态开辟的内存使用free释放 int main() { int a = 10; int* p = &a; free(p); p = NULL; return 0; }
运行发现我们电脑有点死机,这就是因为我们用free释放了栈区的空间,存放在栈区的代码不需要程序员来释放,函数执行结束时,这些存储单元自动被释放。
它的真确书写很简单,只要不要用free释空间即可,即去掉free
int main() { int a = 10; int* p = &a; return 0; }
顾名思义,就是释放了一部分空间,还剩下一部分在内存中没有被释放
//使用free释放一块动态开辟内存空间的一部分 int main() { int* p = (int*)malloc(100); if (p == NULL) { perror("malloc"); return 1; } //使用 int i = 0; for (i = 0; i < 25; i++) { *p = i + 1; //这里p的起始地址发生了变化 p++; } free(p); p = NULL; return 0; }
我们可以看到free(p)释放的只是开辟好的内存空间的最后一个地址指向的空间,因为p的地址发生了改变,所以没有完全释放这100个字节的空间,运行发现我们的电脑在这个程序里也有点死机,可见这是一种非常不友好的行为
解决这个问题,我们要让指针p的地址不发生改变,一直指向开辟好空间的起始地址,这样就能完全释放
int main() { int* p = (int*)malloc(100); if (p == NULL) { perror("malloc"); return 1; } //使用 int i = 0; for (i = 0; i < 25; i++) { *(p+i) = i + 1; } free(p); p = NULL; return 0; }
就是我们对这一块开辟好的动态内存使用完后进行了多次free释放,这是针对指针释放后未被置空(NULL)的时候
//对同一块动态开辟空间多次释放 int main() { int* p = (int*)malloc(100); if (p == NULL) { perror("malloc"); return 1; } //使用p int i = 0; for (i = 0; i < 25; i++) { *(p+i) = i + 1; } //释放 free(p); //使用p int j = 5; while (j--) { *(p + j) = j; } //释放p free(p); return 0; }
我们多次使用free函数释放指针p,同一块动态内存空间不能被释放两次,此时代码就会产生错误
解决这个问题我们只要把p指针释放后在置为空指针(NULL),那么后面我们在释放几次p,代码也不会发生错误。因为free规定,若要释放的指针是空指针,free函数什么事也不做,自然不会报错。
int main() { int* p = (int*)malloc(100); if (p == NULL) { perror("malloc"); return 1; } //使用p int i = 0; for (i = 0; i < 25; i++) { *(p+i) = i + 1; printf("%d ", *(p + i)); } //释放 free(p); p = NULL; //当然我们前面释放了p指向的这块空间,后面我们就不可以再使用p,p此时已经是空指针了 //int j = 5; //while (j--) //{ // *(p + j) = j; //} //释放p free(p); return 0; }
就是说,你申请了一块动态空间忘记释放,这种情况存在内存泄漏
//动态开辟内存空间忘记释放 void Test(void) { //开辟空间 int* p = (int*)malloc(100); if (p == NULL) { perror("malloc"); return; } //使用这块空间 int i = 0; for (i = 0; i < 10; i++) { *(p + i) = i + 1; printf("%d ", *(p + i)); } } int main() { Test(); return 0; }
我们可以看到,在程序中,在函数使用完这块空间后,没有释放这块空间。那么当代码执行完成Test函数时,我们已经来不及释放这块动态空间了,因为p指向的这块动态空间是在Test函数中申请的,p属于局部变量,出了Test函数,p就已经被销毁了,此时就无法知道这100个字节的空间被开辟在什么位置了。
只有当这个程序完全结束时,这100个字节的空间才会被释放
依旧会打印,但存在内存泄漏,不安全
解决这个问题,就是需要我们在使用完这块空间以后及时释放掉,确保不存在内存泄漏
当我们自己使用这块空间时:
//自己用 void Test(void) { //开辟空间 int* p = (int*)malloc(100); if (p == NULL) { perror("malloc"); return; } //使用这块空间 int i = 0; for (i = 0; i < 10; i++) { *(p + i) = i + 1; printf("%d ", *(p + i)); } //释放 free(p); p = NULL; } int main() { Test(); return 0; }
当还有别人要使用这块空间时,此时我们必须注释清楚,给别人一定的提示:
//还有别人使用 //函数内部进行了malloc操作,返回了malloc开辟空间的起始地址,记得释放 int* Test(void) { //开辟空间 int* p = (int*)malloc(100); if (p == NULL) { perror("malloc"); return 1; } //使用这块空间 int i = 0; for (i = 0; i < 10; i++) { *(p + i) = i + 1; printf("%d ", *(p + i)); } return p; 因为还有别人使用,我们在这里不进行释放 //free(p); //p = NULL; } int main() { int* ptr=Test(); //别人开始使用 int i = 0; printf("\n"); for (i = 0; i < 10; i++) { *(ptr + i) = i + 1; printf("%d ", *(ptr + i)); } //使用完成,进行释放 free(ptr); ptr = NULL; return 0; }
运行代码,我们可以看出,这样的操作完全没有问题
这种情况就是说,虽然动态开辟内存函数与free成对使用了,但是在执行free函数之前,代码就已经结束了,此时存在内存泄漏
//动态开辟内存在释放前程序结束 void Test(void) { int* p = (int*)malloc(100); if (p == NULL) { perror("malloc"); return; } //使用这块空间 int i = 0; for (i = 0; i < 10; i++) { *(p + i) = i + 1; printf("%d ", *(p + i)); } //在这里,如果满足条件,开辟好的空间在未被使用之前就结束了,没有释放 if (1) { return; } // 释放 free(p); p = NULL; } int main() { Test(); return 0; }
此时,我们开辟好的动态空间没有被释放,存在内存泄漏,我们写代码的时候要尽量避免这种情况
它不安全
解决这个问题,我们要查看在使用完这块动态空间,同时在后续不在用到这块空间后,这块空间有没有被立马释放
void Test(void) { int* p = (int*)malloc(100); if (p == NULL) { perror("malloc"); return; } //使用这块空间 int i = 0; for (i = 0; i < 10; i++) { *(p + i) = i + 1; printf("%d ", *(p + i)); } // 释放 free(p); p = NULL; 在这里,如果满足条件,开辟好的空间在未被使用之前就结束了,没有释放 //if (1) //{ // return; //} } int main() { Test(); return 0; }
这样就是安全的,不存在内存泄漏
好了,今天的内容就到这里,这也就是我们动态管理空间的绝大部分内容了,大家要好好掌握,我们下期再见!!!