C动态内存管理
前言:不知不觉又过去了很长的一段时间。今天对C语言中的动态内存管理进行一个系统性的总结。
1 为什么要有动态内存分配
在C语言中,使用int,float,double,short等数据内置类型以及数组不是也可以开辟内存空间吗?为什么还要有动态内存分配呢?这是因为以上方式开辟出来的内存空间有两个缺点。
. 空间开辟大小是固定的。
. 数组在声明的时候,必须指定数组的长度,数组空间一旦确定了大小无法调整。
但是对于空间的需求不仅仅是上述情况。有时候我们需要的空间大小在程序运行起来的时候才能知道。那么上述开辟空间的方式就不能满足了。因此C语言中引入了动态内存分配,,让程序员自己申请,释放空间,相对灵活。
2 malloc和free
2.1 malloc函数的介绍
//返回值类型是void*指针,参数类型是size_t,size是申请内存块的大小,单位是字节
//size_t是一个unsigned int类型
void* malloc(size_t size);
malloc函数向内存申请一块连续可用的空间,并返回指向这块内存空间的指针。
. 如果开辟成功,则返回一个指向开辟好空间的指针**。
. 如果失败,则返回一个NULL指针,因此malloc函数的返回值一定要做检查。
. malloc函数的返回类型是void*类型的指针,所以malloc函数并不知道开辟空间的类型,使用的时候由使用者自己来决定。
. 如果参数size为0,malloc的行为是标准未定义的,取决于编译器。
2.2 free函数的介绍
C语言提供了一个free函数,是专门用来释放,回收动态开辟出来的内存空间。
//返回值类型是void,参数类型是void*,ptr是指向先前由malloc或realloc或calloc分配的内存空间
void free(void* ptr);
free函数是用来释放动态开辟的内存。
. 如果参数ptr指向的空间不是动态开辟的,那么free函数的行为是未定义的。
. 如果参数ptr是NULL指针,那么free函数什么事都不做。
#include<stdio.h>
#include<stdlib.h>
int main()
{//动态申请内存空间int* ptr = (int*)malloc(10 * sizeof(int));//检查是否开辟成功if (NULL == ptr){printf("malloc fail\n");exit(-1);}for (int i = 0; i < 10; i++){*(ptr + i) = i + 1;}for (int i = 0; i < 10; i++){printf("%d ", *(ptr + i));}//释放空间free(ptr);//改变ptr指针的指向ptr = NULL;return 0;
}
3 calloc和realloc
3.1 calloc函数的介绍
void* calloc(size_t num,size_t size);
calloc函数也是用来动态开辟内存的。
. calloc函数的功能是为num个大小为size的元素开辟一块空间,并把空间的每个字节初始化为0。
. 与malloc函数的区别在于calloc函数在返回地址之前会将申请的空间每个字节初始化为0。
#include<stdio.h>
#include<stdlib.h>
int main()
{//动态开辟10*sizeof(int)个字节int* p = (int*)calloc(10, sizeof(int));//检查是否开辟成功if (NULL == p){printf("calloc fail\n");exit(-1);}//观察calloc函数开辟空间的内容for (int i = 0; i < 10; i++){printf("%d ", *(p + i));}free(p);p=NULL;return 0;
}
3.2 realloc函数的介绍
void* realloc(void* ptr,size_t size);
realloc函数的功能是对空间的大小进行调整。
. ptr是要调整的内存地址。
. size是调整之后新的大小。
. 返回值是调整之后内存空间的起始地址。
. 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的内存空间。
realloc在调整内存空间时存在两种情况:
1.原有内存空间之后有足够的空间。
2.原有内存空间之后没有足够大的空间。

情况1:在原有空间之后追加空间,原有空间的数据不发生变化。
情况2:原有空间之后没有足够的空间,扩展的方法是:在堆空间上找一个合适大小的连续空间来使用,并将原有空间的数据拷贝一份给新空间,释放原有空间,返回一个新的地址。
#include<stdio.h>
#include<stdlib.h>
int main()
{int* ptr = (int*)calloc(20);if (NULL == ptr){printf("calloc fail\n");exit(-1);}//如果扩容失败会怎么样//原有数据也会丢失,不推荐这种写法ptr = realloc(ptr, 40);//ok?//这种写法更为安全int* tmp = (int*)realloc(ptr, 40);if (NULL == tmp){printf("realloc fail\n");exit(-1);}ptr = tmp;free(ptr);ptr = NULL;return 0;
}
4 常见的动态内存错误
4.1 对NULL指针的解引用操作
void test()
{int* p = (int*)malloc(sizeof(int));if (NULL == p){printf("malloc fail\n");exit(-1);}*p = 20;//如果p是NULL,就会有问题free(p);p = NULL;
}
4.2 对动态开辟空间的越界访问
void test()
{int* p = (int*)malloc(sizeof(int)*10);if (NULL == p){printf("malloc fail\n");exit(-1);}for (int i = 0; i <= 10; i++){*(p + i) = i;//i为10的时候就越界访问了}free(p);p = NULL;
}
4.3 释放一部分动态开辟空间
void test()
{int* p = (int*)malloc(sizeof(int) * 10);if (NULL == p){printf("malloc fail\n");exit(-1);}int i = 0;for (i = 0; i < 10; i++){*(p + i) = i;}for (i = 0; i < 5; i++){printf("%d ", *p);p++;}//此时p不再指向动态开辟空间的起始地址,只释放了一部分空间,p就是野指针free(p);p = NULL;
}
4.4 对非动态开辟空间的释放。
void test()
{int a = 10;int* p = &a;//对非动态开辟内存的释放free(p);p = NULL;
}
4.5 对同一块动态内存多次释放
void test()
{int* p = (int*)malloc(sizeof(int) * 10);if (NULL == p){printf("malloc fail\n");exit(-1);}free(p);free(p);//重复释放p = NULL;
}
4.6 动态开辟内存忘记释放(内存泄漏)
void test()
{int* p = (int*)malloc(sizeof(int) * 10);if (NULL == p){printf("malloc fail\n");exit(-1);}//忘记释放
}
5 动态内存经典笔试题解析
5.1 题目一:
#include<stdio.h>
#include<stdlib.h>
void GetMemory(char* p)
{//动态开辟空间未进行释放,内存泄露p = (char*)malloc(100);
}
void Test(void)
{char* str = NULL;//str作为参数,这里传递的是NULL,形参的改变不会影响实参GetMemory(str);//因此str指向的内容还是NULL,无法对NULL指针进行访问,程序会崩溃strcpy(str, "hello world");printf(str);
}
int main()
{Test();return 0;
}
5.2 题目二:
#include<stdio.h>
#include<stdlib.h>
char* GetMemory(void)
{//局部变量char p[] = "hello world";//调用这个函数时为这个函数创建栈帧空间//调用之后这个函数的栈帧空间被销毁//局部变量也会被销毁,因此返回局部变量的地址会造成野指针return p;
}
void Test(void)
{char* str = NULL;//此时str就是一个野指针,对其进行访问就是非法访问str = GetMemory();printf(str);
}
int main()
{Test();return 0;
}
5.3 题目三:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void GetMemory(char** p, int num)
{//p是一个二级指针,接收的是一级指针变量str的地址//*p就是str*p = (char*)malloc(num);
}
void Test(void)
{char* str = NULL;//传递的是一级指针变量str的地址//形参的改变会影响实参GetMemory(&str, 100);strcpy(str, "hello");printf(str);//唯一的缺点就是没有进行动态内存释放,导致内存泄漏
}
int main()
{Test();return 0;
}
5.4 题目四:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void Test(void)
{char* str = (char*)malloc(100);strcpy(str, "hello");//free之后操作系统回收内存空间,但未将str置空,此时str就是野指针free(str);if (str != NULL){//非法访问strcpy(str, "world");printf(str);}
}
int main()
{Test();return 0;
}
6 柔性数组
在C99中,结构体中最后一个成员允许是未知大小的数组,这就叫做柔性数组。
typedef struct st_type
{int i;int arr[];//柔性数组成员
}type_a;
6.1 柔性数组的特点
. 结构体中柔性数组成员前面必须至少有一个其他成员。
. sizeof 计算结构体大小是不包括柔性数组大小的。
. 包含柔性数组成员的结构用malloc函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的大小。
#include<stdio.h>
#include<stdlib.h>
typedef struct st_type
{//0~3int i;//4 8 4int arr[];//柔性数组成员
}type_a;
int main()
{printf("%zd\n", sizeof(struct st_type));//4return 0;
}
6.2 柔性数组的使用
#include<stdio.h>
#include<stdlib.h>
typedef struct st_type
{int i;int arr[];//柔性数组成员
}type_a;
int main()
{//100*sizeof(int)是为了适应柔性数组成员的大小type_a* p = (type_a*)malloc(sizeof(type_a) + 100 * sizeof(int));if (NULL == p){printf("malloc fail\n");exit(-1);}p->i = 100;int i = 0;for (i = 0; i < 100; i++){p->arr[i] = i;}for (i = 0; i < 100; i++){printf("%d ", p->arr[i]);}free(p);p = NULL;return 0;
}
柔性数组的使用还可以这样完成。
#include<stdio.h>
#include<stdlib.h>
typedef struct st_type
{int i;int *a;//柔性数组成员
}type_a;
int main()
{type_a* p = (type_a*)malloc(sizeof(type_a));if (NULL == p){printf("malloc fail\n");exit(-1);}p->i = 100;p->a = (int*)malloc(p->i * sizeof(int));if (p->a == NULL){printf("p->a malloc fail\n");exit(-1);}int i = 0;for (i = 0; i < 100; i++){p->a[i] = i;}for (i = 0; i < 100; i++){printf("%d ", p->a[i]);}free(p->a);p->a = NULL;free(p);p = NULL;return 0;
}
比较两种方式,哪一种更好呢?第一种方法会更好一点。
1.方便内存释放
2.有利于访问速度(连续的空间有利于提高访问速度,也有利于减少内存碎片)。
7 C/C++中程序内存区域划分

1.栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,
函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的
指令集中,效率很高,但是分配的内存容量有限。栈区主要存放运行函数而
分配的局部变量,函数参数,返回数据,返回地址等。
2.堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由
OS回收。分配方式类似于链表。
3.数据段(静态区)(static):存放全局变量,静态数据。程序结束后由系统释放。
4.代码段:存放函数体(类成员函数和全局函数)的二进制代码。
相关文章:
C动态内存管理
前言:不知不觉又过去了很长的一段时间。今天对C语言中的动态内存管理进行一个系统性的总结。 1 为什么要有动态内存分配 在C语言中,使用int,float,double,short等数据内置类型以及数组不是也可以开辟内存空间吗&…...
系列二、案例实操
一、创建表空间 1.1、概述 在Oracle数据库中,表空间是一个逻辑存储单位,它是Oracle数据库中存储数据的地方。 1.2、超级管理员登录 sqlplus / as sysdba 1.3、创建表空间 create tablespace water_boss datafile C:\Programs\oracle11g\oradata\orcl\…...
Python编码系列—Python状态模式:轻松管理对象状态的变化
🌟🌟 欢迎来到我的技术小筑,一个专为技术探索者打造的交流空间。在这里,我们不仅分享代码的智慧,还探讨技术的深度与广度。无论您是资深开发者还是技术新手,这里都有一片属于您的天空。让我们在知识的海洋中…...
卸载WSL(Ubuntu),卸载linux
禁用 WSL 功能 打开 Windows 功能: 按下 Windows R 打开运行对话框,输入 optionalfeatures,然后按回车。 禁用 WSL: 在弹出的 Windows 功能窗口中,找到 适用于 Linux 的 Windows 子系统(Windows Subsystem…...
Lumerical脚本语言-系统(System)
系统命令包括同操作系统文件系统交互的命令、以及运行脚本文件的命令等。 1、系统命令 命令描述newproject 创建一个新的模拟设计环境 newmode 创建一个新的 MODE 设计环境 save 保存一个 fsp 文件或者 lms 文件 load装载一个 fsp 文件或者 lms 文件 del 删除一个文件 rm 删除一…...
QT 界面编程中使用协程
QT 界面编程中使用协程 一、概述二、集成2.1、编译 Acl2.2、将 Acl 库集成到 QT 项目中2.3、开始编写代码2.3.1、QT 程序初始化时初始化 Acl 协程2.3.2、在界面中创建协程2.3.3、界面程序退出前需要停止协程调度2.3.4、在界面线程中下载数据2.3.5、在协程中延迟创建窗口 2.4、效…...
macOS 开发环境配置与应用开发
✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…...
第13讲 实践:设计SLAM系统
设计一个视觉里程计,理解SLAM软件框架如何搭建,理解视觉里程计设计容易出现的问题以及解决方法。 目录 1、工程目标 2、工程框架 3、实现 附录 1、工程目标 实现一个精简版的双目视觉里程计。由一个光流追踪的前端和一个局部BA的后端组成。 2、工程…...
NeRF2: Neural Radio-Frequency Radiance Fields 笔记
任务:用 NeRF 对无线信号的传播进行建模,建模完成后可以用NeRF网络生成新位置下的信号。生成的信号用于指纹定位、信道估计等下游任务。 核心思路 在视觉 NeRF 的基础上,根据无线信号的特点修改了隐式场模型、渲染函数,网络的输…...
以太网交换安全:MAC地址表安全
一、MAC地址表安全 MAC地址表安全是网络安全中的一个重要方面,它涉及到网络设备的MAC地址表的管理和保护。以下是对MAC地址表安全的详细介绍: (1)基本概念 定义:MAC地址表是网络设备(如交换机࿰…...
CSS综合页布面局案例
写的比较一般,如果想要参考可以点击链接。 CSS综合案例(登录页面)资源-CSDN文库 引言: 我们学习CSS和HTML都是为了想要做一个网页布局,但是每逢上手可能就会需要查阅很多语言,我觉得是没有什么问题的,熟能生巧,编程是需要练的,但是写网页的时候需要实现某个效果时需…...
低代码可视化-UniApp二维码可视化-代码生成器
市面上提供了各种各样的二维码组件,做了一简单的uniapp二维码组件,二维码实现依赖davidshimjs/qrcodejs。 组件特点 跨浏览器支持:利用Canvas元素实现二维码的跨浏览器兼容性,兼容微信小程序、h5、app。 无依赖性:QR…...
Electron 使用 Nodemon 配置自动重启
在Electron项目中,每次修改了代码都需要手动关闭应用,再执行npm start重启应用。 Nodemon 是一个非常实用的工具,主要用于在开发 Node.js 应用时自动监测文件的变化并重新启动服务器。 安装nodemon 开发环境安装nodemon: npm …...
JVM和GC监控技术
一、监控技术简介 JVM是什么?项目里面有JVM吗?JVM跟Tomcat有什么关系?为什么需要去分析JVM? 1. JVM(全称:Java Virtual Machine),Java虚拟机 是Java程序运行的环境,它是一个虚构的计算机&…...
Android中级控件
文章目录 图形定制1.图形Drawable2.形状图像3.九宫格图片4.状态列表图形 选择按钮1.复选框CheckBox2.开关按钮Switch3.单选按钮 文本输入1.编辑框EditText2.焦点变更监听器3.文本变化监听器 对话框1.提醒对话框2.日期对话框DatePickerDialog3.时间对话框TimePickerDialog 图形定…...
WebSocket消息防丢ACK和心跳机制对信息安全性的作用及实现方法
WebSocket消息防丢ACK和心跳机制对信息安全性的作用及实现方法 在现代即时通讯(IM)系统和实时通信应用中,WebSocket作为一种高效的双向通信协议,得到了广泛应用。然而,在实际使用中,如何确保消息的可靠传输…...
生信初学者教程(二十二):Boruta+RF筛选候选标记物
文章目录 介绍加载R包导入数据准备数据机器学习特征筛选数据分割基础模型Boruta特征筛选调参最终分类模型测试集验证标记基因输出结果总结介绍 采用了Boruta结合 RF(Random Forest) 的方法,对差异基因(参考 @sec-different-limma) 进行了特征筛选。通过这种方法,能够从大…...
JVM Class类文件结构
国庆节快乐 2024年10月2日17:49:22 目录 前言 magic 数 文件版本 使用JClassLib观察class文件 一般信息 接口 常量池 字段 方法 常量池计数器 常量池 类型 CONSTANT_Methodref_info CONSTANT_Class_info 类型结构总表 访问标志 类索引, …...
解决 GitHub 文件大小限制的问题
要解决 GitHub 文件大小限制的问题,可以使用 Git Large File Storage (Git LFS)。以下是设置步骤: 安装 Git LFS: 对于 macOS:brew install git-lfs对于 Windows:从 Git LFS官网 下载并安装。 初始化 Git LFSÿ…...
wordpress源码资源站整站打包32GB数据,含6.7W条资源数据
源码太大了,足足32gb,先分享给大家。新手建立资源站,直接用这个代码部署一下,数据就够用了。辅助简单做下seo,一个新站就OK了。 温馨提示:必须按照顺序安装 代码下载...
业务系统对接大模型的基础方案:架构设计与关键步骤
业务系统对接大模型:架构设计与关键步骤 在当今数字化转型的浪潮中,大语言模型(LLM)已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中,不仅可以优化用户体验,还能为业务决策提供…...
conda相比python好处
Conda 作为 Python 的环境和包管理工具,相比原生 Python 生态(如 pip 虚拟环境)有许多独特优势,尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处: 一、一站式环境管理:…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...
【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...
laravel8+vue3.0+element-plus搭建方法
创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...
JVM 内存结构 详解
内存结构 运行时数据区: Java虚拟机在运行Java程序过程中管理的内存区域。 程序计数器: 线程私有,程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。 每个线程都有一个程序计数…...
GitHub 趋势日报 (2025年06月06日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 590 cognee 551 onlook 399 project-based-learning 348 build-your-own-x 320 ne…...
【LeetCode】3309. 连接二进制表示可形成的最大数值(递归|回溯|位运算)
LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 题目描述解题思路Java代码 题目描述 题目链接:LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 给你一个长度为 3 的整数数组 nums。 现以某种顺序 连接…...
LCTF液晶可调谐滤波器在多光谱相机捕捉无人机目标检测中的作用
中达瑞和自2005年成立以来,一直在光谱成像领域深度钻研和发展,始终致力于研发高性能、高可靠性的光谱成像相机,为科研院校提供更优的产品和服务。在《低空背景下无人机目标的光谱特征研究及目标检测应用》这篇论文中提到中达瑞和 LCTF 作为多…...
MySQL的pymysql操作
本章是MySQL的最后一章,MySQL到此完结,下一站Hadoop!!! 这章很简单,完整代码在最后,详细讲解之前python课程里面也有,感兴趣的可以往前找一下 一、查询操作 我们需要打开pycharm …...
