【C语言】动态内存管理

【C语言】动态内存管理
文章目录一、为什么需要动态内存分配二、malloc和free2.1 malloc函数2.2 free函数2.3 使用示例三、calloc和realloc3.1 calloc函数3.2 realloc函数四、常见的动态内存错误4.1 对NULL指针的解引用操作4.2 动态内存的越界访问4.3 对非动态开辟内存使用free释放4.4 释放动态开辟内存的一部分4.5 对同一块动态内存多次释放4.6 动态内存忘记释放内存泄漏五、动态内存笔试题分析5.1 题目15.2 题目25.3 题目35.4 题目4六、柔性数组6.1 柔性数组的定义6.2 柔性数组的特点6.3 柔性数组的使用6.4 柔性数组的优势七、C/C程序内存区域划分一、为什么需要动态内存分配我们已经熟悉的内存开辟方式有两种intval20;// 在栈空间上开辟4个字节chararr[10]{0};// 在栈空间上开辟10个字节的连续空间但这两种方式有明显的局限性空间开辟大小是固定的数组申明时必须指定长度且一旦确定无法调整然而实际编程中我们常常需要在程序运行时才能确定所需空间的大小。例如用户输入数据的数量、处理文件的大小等。这时静态内存分配就无法满足需求了。C语言引入动态内存开辟机制允许程序员根据程序运行时的需要主动申请和释放内存极大地提高了内存使用的灵活性。二、malloc和free2.1 malloc函数C语言提供了malloc函数用于动态内存开辟void*malloc(size_tsize);函数特性向内存申请一块连续可用的空间并返回指向该空间的指针开辟成功返回指向空间的指针失败返回NULL因此必须检查返回值返回值类型为void*使用时需根据需求进行强制类型转换若size为0行为由编译器决定标准未定义2.2 free函数free函数专门用于释放动态开辟的内存voidfree(void*ptr);函数特性用于释放动态开辟的内存空间若ptr指向的空间不是动态开辟的行为未定义若ptr为NULL指针函数不执行任何操作注意malloc和free都声明在stdlib.h头文件中使用时需包含该头文件。2.3 使用示例#includestdio.h#includestdlib.hintmain(){intnum0;scanf(%d,num);int*ptrNULL;ptr(int*)malloc(num*sizeof(int));// 申请num个int类型的空间if(NULL!ptr)// 检查申请是否成功{inti0;for(i0;inum;i){*(ptri)0;// 初始化空间}}free(ptr);// 释放动态内存ptrNULL;// 避免野指针return0;}释放内存后将指针置为NULL是良好的编程习惯可避免出现野指针指向已释放内存的指针。三、calloc和realloc3.1 calloc函数calloc函数也用于动态内存分配原型为void*calloc(size_tnum,size_tsize);函数特性为num个大小为size的元素开辟连续空间自动将空间的每个字节初始化为0与malloc的主要区别是会自动初始化内存为0使用示例#includestdio.h#includestdlib.hintmain(){int*p(int*)calloc(10,sizeof(int));// 申请10个int类型的空间并初始化为0if(NULL!p){inti0;for(i0;i10;i){printf(%d ,*(pi));// 输出10个0}}free(p);pNULL;return0;}当需要对申请的内存进行初始化时使用calloc会比malloc更方便。3.2 realloc函数realloc函数用于调整已动态开辟的内存大小使动态内存管理更加灵活原型为void*realloc(void*ptr,size_tsize);函数特性ptr是要调整的内存地址size是调整后的新大小返回值为调整后内存的起始位置会保留原内存中的数据并移动到新空间内存调整的两种情况原有空间后有足够空间直接在原有空间后追加内存返回原指针原有空间后空间不足在堆中另找合适大小的连续空间将原数据复制过去返回新指针,并且自动把原内存释放如果依旧分配内存失败会返回空指针正确使用方式// 错误方式直接赋值可能导致内存泄漏ptr(int*)realloc(ptr,1000);// 正确方式先判断是否调整成功int*pNULL;prealloc(ptr,1000);if(p!NULL){ptrp;// 调整成功才更新指针}四、常见的动态内存错误动态内存管理容易出现各种错误以下是几种常见情况4.1 对NULL指针的解引用操作voidtest(){int*p(int*)malloc(INT_MAX/4);// 可能申请失败返回NULL*p20;// 若p为NULL会导致程序崩溃free(p);}解决方法始终检查malloc/calloc/realloc的返回值。4.2 动态内存的越界访问voidtest(){inti0;int*p(int*)malloc(10*sizeof(int));if(NULLp){exit(EXIT_FAILURE);}for(i0;i10;i)// i10时越界访问{*(pi)i;}free(p);}解决方法确保访问范围在申请的内存空间内。4.3 对非动态开辟内存使用free释放voidtest(){inta10;int*pa;free(p);// 错误p指向的不是动态内存}解决方法只对动态开辟的内存使用free。4.4 释放动态开辟内存的一部分voidtest(){int*p(int*)malloc(100);p;// p不再指向内存起始位置free(p);// 错误只能释放起始位置}解决方法确保free的是动态内存的起始地址。4.5 对同一块动态内存多次释放voidtest(){int*p(int*)malloc(100);free(p);free(p);// 错误重复释放}解决方法释放后将指针置为NULL再次释放NULL不会有问题。4.6 动态内存忘记释放内存泄漏voidtest(){int*p(int*)malloc(100);if(NULL!p){*p20;}// 没有释放p指向的内存}intmain(){test();while(1);// 程序不结束内存不释放}解决方法尽量做到谁函数开辟谁释放。五、动态内存笔试题分析5.1 题目1voidGetMemory(char*p){p(char*)malloc(100);}voidTest(void){char*strNULL;GetMemory(str);strcpy(str,hello world);printf(str);}分析函数参数传递的是值拷贝GetMemory函数中p的改变不会影响外部的strstr仍为NULL。strcpy对NULL解引用会导致程序崩溃且存在内存泄漏。修改voidGetMemory(char**p)//二级指针接收{*p(char*)malloc(100);}voidTest(void){char*strNULL;GetMemory(str);//传str的地址strcpy(str,hello world);printf(str);free(str);strNULL;}5.2 题目2char*GetMemory(void){charp[]hello world;returnp;}voidTest(void){char*strNULL;strGetMemory();printf(str);}分析p是栈区局部变量函数返回后空间被释放str成为野指针打印结果不确定可能输出乱码。5.3 题目3voidGetMemory(char**p,intnum){*p(char*)malloc(num);}voidTest(void){char*strNULL;GetMemory(str,100);strcpy(str,hello);printf(str);}分析通过二级指针成功修改了str能正确输出hello但存在内存泄漏未释放malloc的空间。5.4 题目4voidTest(void){char*str(char*)malloc(100);strcpy(str,hello);free(str);// 释放内存if(str!NULL)// str仍指向原地址野指针{strcpy(str,world);// 非法访问已释放内存printf(str);}}分析free后未将str置为NULL导致对已释放内存的非法访问结果不确定。六、柔性数组C99标准引入了柔性数组flexible array的概念允许结构体的最后一个元素是未知大小的数组。6.1 柔性数组的定义typedefstructst_type{inti;inta[0];// 柔性数组成员有些编译器需写成int a[];}S;6.2 柔性数组的特点柔性数组成员前必须至少有一个其他成员sizeof返回的结构体大小不包含柔性数组的内存需用malloc动态分配内存且分配的内存要大于结构体大小printf(%d\n,sizeof(S));// 输出4不包含柔性数组6.3 柔性数组的使用intmain(){S*p(S*)malloc(sizeof(S)100*sizeof(int));p-i100;for(inti0;i100;i){p-a[i]i;// 柔性数组获得100个int的空间}free(p);// 一次释放即可return0;}6.4 柔性数组的优势与使用指针的方式相比柔性数组有两个明显优势方便内存释放只需一次free操作即可释放所有内存而指针方式需要分别释放成员和结构体。提高访问速度柔性数组的内存是连续的减少内存碎片有利于提高访问速度。七、C/C程序内存区域划分理解内存区域划分有助于更好地进行动态内存管理栈区stack存放局部变量、函数参数、返回数据等函数执行结束自动释放效率高容量有限内存地址向下增长堆区heap一般由程序员分配和释放若不释放程序结束时可能由OS回收分配方式类似链表内存地址向上增长数据段静态区存放全局变量、静态数据程序结束后由系统释放代码段存放函数体的二进制代码包含只读常量具有只读属性内核空间用户代码不能直接读写用于操作系统内核操作