【C语言】C语言动态内存管理

前言

在C语言编程中,内存管理一直是程序员需要重点关注的领域。动态内存管理更是如此,它不仅涉及到内存的灵活分配和释放,还隐藏着许多潜在的陷阱。本文将从动态内存分配的基础讲起,逐步深入到常见的错误、经典笔试题分析,以及柔性数组的应用,帮助你全面掌握C语言动态内存管理的精髓。

一、为什么需要动态内存分配

在学习C语言的过程中,我们已经熟悉了栈空间的内存分配方式。例如:

int val = 20; // 在栈空间上开辟4个字节
char arr[10] = {0}; // 在栈空间上开辟10个字节的连续空间

这种方式虽然简单,但有两个明显的局限性:
空间大小固定:数组的大小在编译时必须确定,一旦确定就无法调整。
灵活性不足:在某些情况下,我们无法在编译时确定需要的内存大小,例如需要根据用户输入动态分配内存。
为了解决这些问题,C语言引入了动态内存分配。通过动态内存分配,程序员可以在程序运行时根据实际需求申请和释放内存,极大地提高了内存使用的灵活性。

二、malloc和free

2.1 malloc
malloc是C语言中用于动态内存分配的函数,其原型如下:

void* malloc(size_t size);

在这里插入图片描述

功能:向内存申请一块连续可用的空间,并返回指向这块空间的指针。
返回值:
如果申请成功,返回一个指向开辟空间的指针。
如果申请失败,返回NULL。因此,使用malloc时,必须检查返回值是否为NULL。
返回值类型:void*,表示malloc函数并不知道开辟空间的具体类型,使用者需要自行决定。
例如:

#include <stdio.h>
#include <stdlib.h>int main()
{int num = 0;scanf("%d", &num); // 用户输入需要分配的整数个数int* ptr = NULL;ptr = (int*)malloc(num * sizeof(int)); // 动态分配内存if (NULL != ptr) // 检查是否分配成功{int i = 0;for (i = 0; i < num; i++){*(ptr + i) = 0; // 初始化内存}}free(ptr); // 释放内存ptr = NULL; // 将指针置为NULL,避免野指针return 0;
}

在这里插入图片描述

2.2 free
free函数用于释放动态分配的内存,其原型如下:

void free(void* ptr);

功能:释放由malloc、calloc或realloc分配的内存。
注意事项:
如果ptr指向的内存不是动态分配的,free的行为是未定义的。
如果ptr为NULL,free不会执行任何操作。
malloc和free都声明在stdlib.h头文件中。

三、calloc和realloc

3.1 calloc
calloc也是C语言中用于动态内存分配的函数,其原型如下:

void* calloc(size_t num, size_t size);

功能:为num个大小为size的元素分配内存,并将内存中的每个字节初始化为0。
与malloc的区别:calloc会在返回地址之前将申请的空间的每个字节初始化为0。
例如:

#include <stdio.h>
#include <stdlib.h>int main()
{int* p = (int*)calloc(10, sizeof(int)); // 分配10个整数空间,并初始化为0if (NULL != p){int i = 0;for (i = 0; i < 10; i++){printf("%d ", *(p + i)); // 输出初始化后的值}}free(p); // 释放内存p = NULL; // 避免野指针return 0;
}

在这里插入图片描述

输出结果为:
0 0 0 0 0 0 0 0 0 0
3.2 realloc
realloc函数用于调整动态分配的内存大小,其原型如下:

void* realloc(void* ptr, size_t size);

在这里插入图片描述

功能:调整由ptr指向的内存块的大小为size。
注意事项:
如果ptr为NULL,realloc的行为等同于malloc(size)。
如果size为0,realloc的行为等同于free(ptr)。
如果调整成功,返回调整后的内存块的指针;如果失败,返回NULL,并且原内存块保持不变。
realloc在调整内存大小时,会根据内存空间的可用性选择以下两种情况之一:
原有空间之后有足够的空间:直接在原有内存之后追加空间,数据保持不变。
原有空间之后没有足够的空间:在堆空间上另找一个合适大小的连续空间,将原数据复制到新空间,并返回新的内存地址。
例如:

#include <stdio.h>
#include <stdlib.h>int main()
{int* ptr = (int*)malloc(100); // 初始分配100字节if (ptr != NULL){// 业务处理}else{return 1;}// 扩展容量int* p = NULL;p = (int*)realloc(ptr, 1000); // 调整为1000字节if (p != NULL){ptr = p; // 更新指针}// 业务处理free(ptr); // 释放内存return 0;
}

四、常见的动态内存错误

4.1 对NULL指针的解引用操作

void test()
{int* p = (int*)malloc(INT_MAX / 4); // 可能分配失败*p = 20; // 如果p为NULL,会导致程序崩溃free(p);
}

解决方法:在使用指针之前,必须检查其是否为NULL。
4.2 对动态开辟空间的越界访问

void test()
{int i = 0;int* p = (int*)malloc(10 * sizeof(int)); // 分配10个整数空间if (NULL == p){perror("malloc")return 1;}for (i = 0; i <= 10; i++) // 越界访问{*(p + i) = i;}free(p);
}

解决方法:严格控制访问范围,避免越界。
4.3 对非动态开辟内存使用free释放

void test()
{int a = 10;int* p = &a;free(p); // 错误:不能释放非动态分配的内存
}

解决方法:free只能用于释放由malloc、calloc或realloc分配的内存。
4.4 使用free释放一块动态开辟内存的一部分

void test()
{int* p = (int*)malloc(100);p++; // p不再指向动态内存的起始位置free(p); // 错误:不能释放非起始位置的内存
}

解决方法:free必须释放动态分配的内存的起始位置。
4.5 对同一块动态内存多次释放

void test()
{int* p = (int*)malloc(100);free(p);free(p); // 错误:重复释放
}

解决方法:释放内存后,将指针置为NULL,避免重复释放。
4.6 动态开辟内存忘记释放(内存泄漏)

void test()
{int* p = (int*)malloc(100);if (NULL != p){*p = 20;}
}
int main()
{test();while (1); // 模拟长时间运行
}

解决方法:动态分配的内存必须在不再使用时释放,避免内存泄漏。

五、动态内存经典笔试题分析

5.1 题目1

void GetMemory(char* p)
{p = (char*)malloc(100);
}
void Test(void)
{char* str = NULL;GetMemory(str);strcpy(str, "hello world");printf("%s", str);
}

问题:运行Test函数会有什么样的结果?
答案:程序会崩溃。对NULL指针解引用操作,程序会崩溃。内存泄露。
5.2 题目2

char* GetMemory(void)
{char p[] = "hello world";return p;
}
void Test(void)
{char* str = NULL;str = GetMemory();printf("%s", str);
}

问题:运行Test函数会有什么样的结果?
答案:程序会输出垃圾数据或崩溃。GetMemory函数返回的是局部数组p的地址,而局部数组在函数返回后会被销毁,因此str指向的是无效内存。
5.3 题目3

void GetMemory(char** p, int num)
{*p = (char*)malloc(num);
}
void Test(void)
{char* str = NULL;GetMemory(&str, 100);strcpy(str, "hello");printf("%s", str);
}

问题:运行Test函数会有什么样的结果?
答案:程序正常运行,输出hello。GetMemory函数通过指针的指针p正确地修改了str的值。但是会造成内存泄露
5.4 题目4

void Test(void)
{char* str = (char*)malloc(100);strcpy(str, "hello");free(str);if (str != NULL){strcpy(str, "world");printf("%s", str);}
}

问题:运行Test函数会有什么样的结果?
答案:程序可能会崩溃或输出垃圾数据。free释放了str指向的内存后,str变成了野指针,再次访问会导致未定义行为。

六、柔性数组

柔性数组是C99标准中引入的一种特殊数组类型,允许结构体的最后一个成员是一个未知大小的数组。例如:

struct st_type
{int i;int a[0]; // 柔性数组成员
};

柔性数组的特点如下:
结构体中柔性数组成员前必须至少有一个其他成员。
sizeof返回的结构体大小不包括柔性数组的内存。
包含柔性数组成员的结构体必须通过malloc动态分配内存,并且分配的内存应该大于结构体的大小,以适应柔性数组的预期大小。
6.1 柔性数组的使用

#include <stdio.h>
#include <stdlib.h>typedef struct st_type
{int i;int a[0]; // 柔性数组成员
} type_a;int main()
{type_a* p = (type_a*)malloc(sizeof(type_a) + 100 * sizeof(int)); // 分配内存p->i = 100;for (int i = 0; i < 100; i++){p->a[i] = i; // 使用柔性数组}free(p); // 释放内存return 0;
}

6.2 柔性数组的优势
柔性数组相比传统的指针成员有以下优势:
方便内存释放:一次性分配内存,用户只需调用一次free即可释放所有内存。
提高访问速度:连续的内存有利于提高访问速度,减少内存碎片。

七、C/C++程序内存区域划分

C/C++程序的内存分为以下几个区域:
栈区(stack):用于存储函数内的局部变量、函数参数、返回数据和返回地址等。栈内存分配效率高,但容量有限。
堆区(heap):由程序员动态分配和释放内存,若程序员不释放,程序结束时可能由操作系统回收。
数据段(静态区):存放全局变量和静态数据,程序结束后由系统释放。
代码段:存放函数体的二进制代码。
在这里插入图片描述

八、总结

动态内存管理是C语言编程中的重要组成部分,它为程序员提供了极大的灵活性,但也带来了许多潜在的风险。通过本文的介绍,相信你已经对动态内存管理有了更深入的理解。在实际编程中,一定要注意避免常见的错误,合理使用malloc、calloc、realloc和free等函数,确保程序的稳定性和安全性。

希望本文对你有所帮助!如果还有其他问题,欢迎在评论区留言讨论。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/78149.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

expres路由模块化

Express 路由模块化是实际开发中非常重要的一部分&#xff0c;可以让你的项目结构更清晰、维护更方便。 &#x1f9f1; 一、为什么要模块化&#xff1f; 随着项目变大&#xff0c;如果所有路由都写在 app.js 中&#xff0c;会很乱。使用模块化后可以&#xff1a; 功能解耦&a…

C语言——填充矩阵

C语言——填充矩阵 一、问题描述二、格式要求1.输入形式2.输出形式3.样例 三、实验代码 一、问题描述 编程实现自动填充nn矩阵元素数值&#xff0c;填充规则为&#xff1a;从第一行最后一列矩阵元素开始按逆时针方向螺旋式填充数值1&#xff0c;2&#xff0c;…&#xff0c;nn…

零基础上手Python数据分析 (22)案例实战]之利用 Matplotlib Seaborn 进行电商销售数据可视化分析

写在前面 —— 图表为刃,洞察先行!综合运用 Pandas、Matplotlib 与 Seaborn,点亮数据价值 本篇通过一个完整的案例实战,体验如何将数据分析与数据可视化紧密结合,让冰冷的数据转化为生动、直观、富有洞察力的视觉故事! 案例目标: 本篇博客将延续我们在第 17 篇案例中…

Java开发经验总结

只要删繁、捋清脉络&#xff0c;才能掌握本质&#xff01;只有创新才有价值&#xff0c;保持创新、保持学习&#xff01; 计划&#xff1a;UNIAPPSPRINGBOOT学习、SPRINGBOOTVUE新版学习、页面展示学习、PYTHON。 ***********************************************************…

深入解析:RocketMQ、RabbitMQ和Kafka的区别与使用场景

互联网大厂Java求职者面试&#xff1a;RocketMQ、RabbitMQ和Kafka的深入解析 故事场景&#xff1a;严肃且专业的面试官与架构师程序员马架构 在一家知名的互联网大厂&#xff0c;Java求职者正在接受一场严格的面试。面试官是一位经验丰富的技术专家&#xff0c;他将通过多轮提…

使用vue2开发一个医疗预约挂号平台-前端静态网站项目练习

对于后端开发的我&#xff0c;最近一直在学习前端开发&#xff0c;除了要学习一些前端的基础知识外&#xff0c;肯定少不了一些前端项目练习&#xff0c;就通过前端的编程知识 就简单做一个医疗预约挂号前端静态页面。这个网站主要是使用了vue2 的相关技术实现的。 主要实现了这…

MongoDB(docker版)备份还原

docker启动MongoDB docker run -d -p 27017:27017 --name my-mongo -v /mongodb/db:/data/db mongo备份MongoDB 使用mongodump备份数据库时&#xff0c;默认会将备份数据保存在当前工作目录下的dump文件夹中。 docker容器中默认备份在当前工作目录&#xff0c;所以此处指定当…

zkPass案例实战之合约篇

目录 一、contracts/contracts/ProofVerifier.sol 1. License 和 Solidity 版本 2. 导入依赖 3. 合约声明和默认分配器地址 4. 验证证明 5. 验证分配器签名 6. 验证验证者签名 7. 签名前缀处理 8. 签名恢复 总结 二、contracts/contracts/SampleAttestation.sol 1. …

ElasticSearch:高并发场景下如何保证读写一致性?

在Elasticsearch高并发场景下&#xff0c;可以通过以下多种方式来保证读写一致性&#xff1a; 等待主分片和副本分片都确认&#xff08;类似半同步机制&#xff09; 设置consistency参数&#xff1a;在写操作时&#xff0c;可以设置consistency参数来控制写操作的一致性级别。…

8、constexpr if、inline、类模版参数推导、lambda的this捕获、初始化列表、namespace---c++17

一、constexpr if&#xff1a;编译时条件分支 作用&#xff1a;在模板编程中&#xff0c;根据条件在编译时选择不同的代码路径&#xff0c;无需特化版本或复杂SFINAE技巧[替代SFINAE]。[SFINAE将在模版元编程再讲。下个月了。] 注意&#xff1a;默认使用了隐式inline 基本语法…

【Java设计模式及实践学习-第4章节-结构型模式】

第4章节-结构型模式 笔记记录 1. 适配器模式2. 代理模式3. 装饰器模式4. 桥接模式5. 组合模式6. 外观模式7. 享元模式8. 总结 1. 适配器模式 2. 代理模式 3. 装饰器模式 4. 桥接模式 5. 组合模式 6. 外观模式 7. 享元模式 Java语言中的String字符串就使用了享元模式&…

unity基础自学2.3:移动和抓握物品

文章目录 前言&#xff1a;1、基础配置①XR Interaction Toolkit②创建一个XR场景③示例文件实现④ 一键配置&#xff08;PICO Building Blocks&#xff09; 2、射线移动物品和抓握物品方法一&#xff1a;Grab Interactable方法二&#xff1a;prefab 3、Box Collider的作用与使…

pytest基础-new

规范 1、首先创建 py 文件命名以 test_ 开始或者以 _test 结尾 2、若是新建类&#xff0c;测试类需要以 Test_开头 3、测试用例&#xff08;方法&#xff09;需要以 test_开头 assert 断言 assert xx&#xff1a;判断 xx 为真 assert not xx&#xff1a;判断 xx 不为真 asse…

【华为OD机试真题】232、统计射击比赛成绩 | 机试真题+思路参考+代码分析(C++)

题目描述 给定一个射击比赛成绩单,包含多个选手若干次射击的成绩分数,请对每个选手按其最高3个分数之和进行降序排名,输出降序排 名后的选手ID序列 条件如下: 1.一个选手可以有多个射击成绩的分数,且次序不固定 2.如果一个选手成绩少于3个,则认为选手的所有成绩无效,排名…

⭐Unity 开发 | 如何通过 NTP 网络时间实现精准的跨平台时间同步【附完整源码 + UI 模块 + 偏差分析】

&#x1f3ae; 项目实战 | 实现一套精确、可视化的游戏时间同步机制&#xff0c;让你的多人在线游戏摆脱“时间不一致”噩梦&#xff01; 效果如图&#xff1a; &#x1f4cc; 一、前言&#xff1a;为什么不能只信本地时间&#xff1f; 在 Unity 游戏开发中&#xff0c;时间几…

Vue3 Composition API与十大组件开发案例详解

文章目录 一、Vue3核心API解析1.1 Composition API优势1.2 核心API 二、十大组件开发案例案例1&#xff1a;响应式表单组件案例2&#xff1a;动态模态框&#xff08;Teleport应用&#xff09;案例3&#xff1a;可复用列表组件案例4&#xff1a;全局状态通知组件案例5&#xff1…

Kafka 详细解读

1. Producer&#xff08;生产部卷王&#xff09; 职责&#xff1a;往 Kafka 里疯狂输出数据&#xff0c;KPI 是「日抛式消息海啸」 职场人设&#xff1a; 白天开会画饼&#xff0c;深夜写周报的奋斗逼&#xff0c;口头禅是「这个需求今晚必须上线&#xff01;」代码里的「福报…

LicheeRV Nano 与Ubuntu官方risc-v 镜像混合

LicheeRV Nano 官方给的镜像并没有unbutu, unbutu官方有一个基于 LicheeRV Dock的镜像&#xff0c;想象能否将二者混合 &#xff08;1&#xff09;刷 LicheeRV Dock的镜像 nano无法启动 &#xff08;2&#xff09;将nano的boot分区替换掉 LicheeRV Dock的rootfs以外的分区也…

【模板匹配】图像处理(OpenCV)-part10

19.1模板匹配 模板匹配就是用模板图&#xff08;通常是一个小图&#xff09;在目标图像&#xff08;通常是一个比模板图大的图片&#xff09;中不断的滑动比较&#xff0c;通过某种比较方法来判断是否匹配成功,找到模板图所在的位置。 不会有边缘填充。 类似于卷积&#xff0c…

HTML:表格数据展示区

<!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>人员信息表</title><link rel"styl…