尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

C语言:指针和动态内存管理

C语言:指针和动态内存管理 文章目录前言本文旨在系统性地介绍C语言中的指针和动态内存管理。一、动态内存管理二、malloc函数三、free函数四、calloc函数五、realloc函数六、动态内存管理常见错误6.1、未判断返回值对 NULL 解引用6.2、越界访问6.3、释放非动态内存6.4、释放部分动态内存6.5、多次释放同一块内存6.6、内存泄漏七、柔性数组7.1、柔性数组的特点7.2、柔性数组的使用前言本文旨在系统性地介绍C语言中的指针和动态内存管理。一、动态内存管理到目前为止我们学习过两种向内存申请空间的方式创建变量和创建数组。代码示例intg_val100;// 全局变量程序结束时才释放intmain(){intval20;// 栈区出了作用域自动释放chararr[10]{0};// 栈区大小固定不能调整return0;}这种方式存在以下局限①大小固定数组长度一旦确定无法灵活调整②生命周期受限栈区变量出作用域即被回收③全局变量释放晚全局变量只能等程序结束才释放。程序对空间的需求不止于此有时我们需要①空间大小可以灵活调整②内存的申请和释放更加自由。因此C语言引入了动态内存开辟让程序员自己管理内存在堆区上申请空间更加灵活。涉及的函数malloc、free、calloc、realloc。二、malloc函数malloc 用于在堆区动态申请一块连续可用的内存空间。代码示例#includestdlib.h// 需要此头文件void*malloc(size_tsize);功能向内存的堆区申请一块连续可用的空间并返回指向这块空间的起始地址。参数size要分配的内存块大小字节数。参数为 0 时malloc 的行为由编译器决定标准未定义。返回值①开辟成功返回这块空间的起始地址②开辟失败如内存不足返回 NULL因此返回值必须做检查③返回类型为 void*具体使用时由使用者决定类型转换。代码示例int*p(int*)malloc(10*sizeof(int));if(pNULL){// 处理内存分配失败}三、free函数free 用于释放之前通过动态内存分配函数malloc、calloc、realloc申请的内存空间。代码示例#includestdlib.hvoidfree(void*ptr);功能释放动态申请的内存空间。参数ptr指向要释放的内存块的指针。参数 ptr 不是动态开辟的空间free 的行为是未定义的参数 ptr 是 NULL函数什么都不做。代码示例#includestdio.h#includestdlib.hintmain(){intnum0;scanf(%d,num);// 申请动态内存int*ptr(int*)malloc(num*sizeof(int));// 检查是否申请成功if(ptrNULL){printf(malloc 失败\n);return1;}// 使用内存for(inti0;inum;i){ptr[i]0;// 等价于 *(ptr i) 0}// 释放动态内存free(ptr);// 将堆内存归还给操作系统ptrNULL;// 手动置 NULL避免悬空指针return0;}free 只是释放了 ptr 指向的堆内存但不会自动将 ptr 置为 NULL。悬空指针指针曾经有效但指向的内存已被释放或失效指针仍保留原地址。代码示例free(ptr);// 内存已释放但 ptr 仍保留原地址// 此时 ptr 就是悬空指针ptrNULL;// 手动置 NULL安全四、calloc函数calloc 用于动态内存分配与 malloc 类似但会将申请的空间初始化为 0。代码示例#includestdlib.hvoid*calloc(size_tnum,size_tsize);功能为 num 个大小为 size 的元素开辟空间并将每个字节初始化为 0。参数num元素个数size每个元素的字节数。返回值成功返回起始地址失败返回 NULL。对比malloccalloc参数size总字节数num × size元素个数 × 单个大小初始化内容不确定随机值每个字节初始化为 0代码示例#includestdio.h#includestdlib.hintmain(){// 申请 10 个 int 大小的空间并初始化为 0int*p(int*)calloc(10,sizeof(int));if(p!NULL){for(inti0;i10;i){printf(%d ,p[i]);// 全部输出 0}printf(\n);}free(p);pNULL;return0;}五、realloc函数realloc 用于调整已申请的动态内存空间大小使内存管理更加灵活。代码示例#includestdlib.hvoid*realloc(void*ptr,size_tsize);功能重新调整之前分配的内存块大小不丢失原有数据参数ptr原内存块起始地址若为 NULL 则等同 mallocsize调整后的新大小字节数。返回值成功返回重新分配的内存块地址可能与原地址不同失败返回 NULL原内存块保持不变。realloc 调整内存大小时有两种处理方式①原空间后有足够空间直接追加 返回原地址②原空间后空间不足寻找新空间 → 拷贝旧数据 → 释放旧空间 → 返回新地址。代码示例错误用法不安全int*ptr(int*)malloc(100);if(ptrNULL)return1;// 错误若 realloc 失败返回 NULL原 ptr 地址丢失ptr(int*)realloc(ptr,1000);// ptr 变成 NULL原来的内存无法释放正确用法安全int*ptr(int*)malloc(100);if(ptrNULL)return1;// 正确用临时指针接收 realloc 返回值int*p(int*)realloc(ptr,1000);if(p!NULL){ptrp;// 成功更新 ptrpNULL;// 临时指针置空}else{// 扩展失败原 ptr 仍有效可继续使用printf(realloc 失败\n);}free(ptr);ptrNULL;六、动态内存管理常见错误6.1、未判断返回值对 NULL 解引用malloc、calloc、realloc 申请失败时返回 NULL若不判断直接使用会导致对 NULL 指针解引用。正确做法使用前判断 p ! NULL。代码示例voidtest(){int*p(int*)malloc(INT_MAX/4);*p20;// 若 p 为 NULL程序崩溃free(p);pNULL;}6.2、越界访问动态申请的空间有固定大小访问超出范围会导致未定义行为。代码示例voidtest(){int*p(int*)malloc(10*sizeof(int));if(pNULL)return;for(inti0;i10;i)// i10 时越界{p[i]i;}free(p);pNULL;}6.3、释放非动态内存free 只能释放动态申请的内存堆区不能释放栈区或全局变量。代码示例voidtest(){inta10;// 栈区变量int*pa;free(p);// 错误,p 指向栈区}6.4、释放部分动态内存free 的指针必须指向动态内存的起始地址。代码示例voidtest(){int*p(int*)malloc(100);p;// p 不再指向起始地址free(p);// 错误}6.5、多次释放同一块内存一块动态内存只能释放一次重复释放会导致未定义行为。代码示例voidtest(){int*p(int*)malloc(100);free(p);free(p);// 重复释放}6.6、内存泄漏动态分配的内存使用完毕后未释放导致内存一直被占用。代码示例voidtest(){int*p(int*)malloc(100);if(p!NULL){*p20;}// 忘记 free(p)内存泄漏}intmain(){test();while(1);// 程序持续运行泄漏的内存无法回收return0;}内存泄漏是指动态分配的内存通过 malloc、calloc、realloc 获取在使用完毕后未被释放导致这块内存一直被程序占用直到程序结束才能回收。原因①用 free分配内存后未执行释放操作②提前返回或异常退出 在分配与释放之间通过 return、exit、goto 跳过了释放语句③丢失指针 指针被重新赋值或覆盖原内存块地址丢失无法释放。后果①资源浪费泄漏的内存在程序运行期间无法回收可用内存逐渐减少②性能下降系统频繁进行内存换页和分配程序运行变慢③程序崩溃泄漏累积耗尽内存后malloc 失败程序异常终止④难以调试泄漏通常在长时间运行后才暴露定位困难。七、柔性数组柔性数组是 C99 标准引入的一种特性允许结构体的最后一个成员是未知大小的数组。代码示例//方式一使用 [0]部分编译器支持structst_type1{inti;inta[0];// 柔性数组成员};// 方式二使用 []C99 标准写法structst_type2{inti;inta[];// 柔性数组成员};7.1、柔性数组的特点柔性数组成员前必须至少有一个其他成员sizeof(结构体) 计算时不包含柔性数组的大小分配空间时需 sizeof(结构体) 柔性数组大小代码示例structst_type{inti;inta[0];// 柔性数组成员};intmain(){printf(%d\n,sizeof(structst_type));// 输出 4return0;}7.2、柔性数组的使用代码1柔性数组方式代码示例#includestdio.h#includestdlib.hstructst_type{inti;inta[0];// 柔性数组成员};intmain(){structst_type*p(structst_type*)malloc(sizeof(structst_type)100*sizeof(int));if(pNULL){perror(malloc);return1;}// 业务处理p-i100;for(inti0;i100;i){p-a[i]i;}// 打印验证printf(i %d\n,p-i);for(inti0;i10;i)// 打印前10个{printf(%d ,p-a[i]);}printf(\n);// 释放空间free(p);pNULL;return0;}代码2指针成员方式#includestdio.h#includestdlib.hstructst_type{inti;int*p_a;// 指针成员};intmain(){// 第一次分配结构体structst_type*p(structst_type*)malloc(sizeof(structst_type));if(pNULL){perror(malloc);return1;}p-i100;// 第二次分配数组空间int*ptr(int*)malloc(p-i*sizeof(int));if(ptrNULL){perror(malloc 2);free(p);// 释放已分配的结构体pNULL;return2;}p-p_aptr;ptrNULL;// 业务处理for(inti0;i100;i){p-p_a[i]i;}// 打印验证printf(i %d\n,p-i);for(inti0;i10;i)// 打印前10个{printf(%d ,p-p_a[i]);}printf(\n);// 释放空间注意顺序先释放内部指针再释放结构体free(p-p_a);p-p_aNULL;free(p);pNULL;return0;}柔性数组的两大优势优势一方便内存释放方式释放操作风险柔性数组只需 free( p ) 一次不易遗漏指针成员需先 free(p-p_a) 再 free( p )容易忘记释放内部指针如果结构体作为函数返回值返回给用户用户可能不知道需要释放内部指针导致内存泄漏。柔性数组只需一次 free更安全。优势二提高访问速度减少内存碎片柔性数组的内存是连续的CPU 缓存友好访问更快只需一次内存分配减少堆区的内存碎片减少内存分配次数降低分配失败的风险。
返回列表