💕"痛苦难以避免,而磨难可以选择。"-->村上春树💕
作者:Mylvzi
文章主要内容:动态内存管理
前言:为什么要进行动态内存管理-->之前开辟内存的方式对于内存的使用过于局限;
一.动态内存函数的介绍
所有的与动态内存分配有关的函数都包含于<stdlib.h>这个头文件之中
1.malloc函数-->动态内存开辟函数
作用:在堆区之中申请size字节大小的空间,并返回开辟内存空间的起始地址;
注意:
1.在申请内存的过程中有可能申请失败,如果申请失败,会返回NULL;(一定要进行判断,对空指针的解引用是非法的)
2.malloc的返回值是void*,使用者可以根据自身需求进行强制类型转换;
3.动态开辟的内存并不会自动销毁(还给操作系统),销毁有两种方式,使用free函数释放内存空间,程序退出;
2.free函数-->动态内存释放函数
作用:释放之前已经被动态内存函数(malloc,calloc,realoc)开辟的内存空间,将内存空间还给操作系统
注意:
1.只能释放动态开辟的内存,不能释放在栈区开辟的内存:
2.释放之后,要及时对释放空间的起始地址置于NULL,避免野指针的出现
代码示例:
int main() { int* p = (int*)malloc(40);//在堆区申请40byte空间 //判断malloc函数是否成功在堆区申请到空间,失败,返回NULL //成功,返回所申请空间的初始地址 if (p == NULL) { perror("malloc");//打印错误信息 return 1; } //使用完毕,使用free函数对内存空间进行释放(和栈区内存的申请是不同的) free(p); p = NULL;//及时将释放空间的起始地址置于空指针 /*free函数只能释放动态开辟的内存空间*/ //int a = 10; //int* ptr = &a; //free(ptr);//err return 0; }
3.calloc函数-->作用和malloc函数相同,进行动态内存开辟
作用:向内存申请num个size字节大小的内存空间,并把内存空间的内容初始化为0 (malloc函数并不会进行初始化)
代码示例:
//calloc函数-->void* calloc(size_t num,size_t size) int main() { //int arr[10]; int* p = (int*)calloc(10, 4); if (p == NULL) { perror("calloc"); return 1; } //打印数据 int i = 0; for (i = 0; i < 10; i++) { printf("%d ", p[i]);//打印10个0 } //使用完毕,释放内存空间 free(p); p = NULL; return 0; }
4.realloc函数-->调整动态内存大小的函数(重要,实现动态内存分配的核心函数)
作用:将ptr所指向的内存空间的大小改为size字节(增容)
但是,堆区的内存空间是有限的,在重新分配内存大小时,可能会出现内存空间不足的情况,导致返回值有三种情况;
1.返回旧空间的起始地址
2.返回新开辟空间的起始地址
3.返回NULL
当返回值不确定时,重新创建一个变量来接受
代码示例:
//realloc函数 //void* realloc(void* ptr ,size_t size) int main() { //动态开辟一块内存空间 int* p = (int*)malloc(40); if (p == NULL) { perror("malloc"); return 1; } //赋值 int i = 0; for (i = 0; i < 10; i++) { p[i] = i + 1; } //增容 int* ptr = (int*)realloc(p, 80);//增容为80byte if (ptr != NULL)//只要不是NULL,就证明开辟成功 { p = ptr; ptr = NULL; } else { perror("realloc"); return 1; } for (i = 0; i < 20; i++) { printf("%d ", p[i]); } //使用完毕 free(p); p = NULL; return 0; }
二.动态内存常见错误
1.对NULL的解引用操作(要检测返回值是否为NULL, 是NULL应该直接退出函数)
int* p = (int*)malloc(40); *p = 20;//未检测返回值是否为NULL free(p); p = NULL;
2.对动态开辟空间的越界访问(不能超过你申请内存空间的大小)
int* p = (int*)malloc(20);//申请了一个能容纳5个int类型数据的空间 if (p == NULL) { perror("malloc"); return 1; } int i = 0; for (i = 0; i < 10; i++) { p[i] = i + 1;//超过内存限制 }
3. 对非动态开辟内存进行free释放(free只能释放动态开辟的内存)
int a = 10; int* p = &a; free(p); p = NULL;
4.使用free释放一块动态开辟内存的一部分 (不能占着茅坑不拉屎)
int* p = (int*)malloc(40); if (p == NULL) { perror("malloc"); return 1; } p++; free(p);//ERR p此时已经不再是起始位置,不能释放内存的一部分 p = NULL;
5.对同一块动态内存多次释放
int* p = (int*)malloc(40); if (p == NULL) { perror("malloc"); return 1; } free(p); free(p);//err 已经释放过的不能再被释放
6.动态开辟内存忘记释放(内存泄漏)
void test() { int* p = (int*)malloc(100); if (NULL != p) { *p = 20; } } int main() { test(); while (1); }
总结:动态内存的开辟虽然方便,但它是把双刃剑,会带来其他危险,在使用过程中要注意规范,养成以下习惯可以规避一些风险
1.在使用动态内存函数开辟完空间后,及时判断是否申请成功(p是否为NULL)
2.要及时释放动态开辟的内存(谨记malloc和free是成对出现的),并在释放后将地址置于NULL,避免野指针的出现;
3.在接受realloc函数的返回值时,最好创建一个新的变量来接收,并判断是否增容成功
补充一点:
free函数的参数中只有一个void* 类型的地址,并未告诉你对应空间的大小,那他是如何精准释放空间呢?原因在于,在malloc函数分配内存时,系统会在你所申请的内存之前(或之后,具体看编译环境)设置一个“头部信息”,free函数在得到起始地址后,会根据头部信息的内容知道内存的具体大小,从而实现对内存的精准释放
三.动态内存分配笔试题讲解
1.题目一:
解决方案:你想改变实参str的值,为实参开辟空间,改变实参,要传递实参的地址!
void GetMemory(char** p)//使用二级指针存放str的地址 { *p = (char*)malloc(100);//对p解引用,得到str所在的空间 } //为str分配足够的空间 void Test(void) { char* str = NULL; GetMemory(&str); strcpy(str, "hello world"); printf(str);//输出hello world }
2.题目二:
注意:返回栈区空间地址,产生野指针,非法访问内存
3.题目三:
free之后一定要及时将旧地址置为空指针
四.c/c++程序内存分配
五.总结
总结:动态内存分配是一种管理内存的重要方式,要了解与动态内存管理有关的函数(malloc,calloc,realloc,free),熟记动态内存分配过程中的危险(是否分配成功,realloc函数的返回值有三种情况),了解基本的内存分配知识;感谢大家观看