当前位置: 首页 > news >正文

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动态内存管理

前言&#xff1a;不知不觉又过去了很长的一段时间。今天对C语言中的动态内存管理进行一个系统性的总结。 1 为什么要有动态内存分配 在C语言中&#xff0c;使用int&#xff0c;float&#xff0c;double&#xff0c;short等数据内置类型以及数组不是也可以开辟内存空间吗&…...

系列二、案例实操

一、创建表空间 1.1、概述 在Oracle数据库中&#xff0c;表空间是一个逻辑存储单位&#xff0c;它是Oracle数据库中存储数据的地方。 1.2、超级管理员登录 sqlplus / as sysdba 1.3、创建表空间 create tablespace water_boss datafile C:\Programs\oracle11g\oradata\orcl\…...

Python编码系列—Python状态模式:轻松管理对象状态的变化

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…...

卸载WSL(Ubuntu),卸载linux

禁用 WSL 功能 打开 Windows 功能&#xff1a; 按下 Windows R 打开运行对话框&#xff0c;输入 optionalfeatures&#xff0c;然后按回车。 禁用 WSL&#xff1a; 在弹出的 Windows 功能窗口中&#xff0c;找到 适用于 Linux 的 Windows 子系统&#xff08;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 开发环境配置与应用开发

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…...

第13讲 实践:设计SLAM系统

设计一个视觉里程计&#xff0c;理解SLAM软件框架如何搭建&#xff0c;理解视觉里程计设计容易出现的问题以及解决方法。 目录 1、工程目标 2、工程框架 3、实现 附录 1、工程目标 实现一个精简版的双目视觉里程计。由一个光流追踪的前端和一个局部BA的后端组成。 2、工程…...

NeRF2: Neural Radio-Frequency Radiance Fields 笔记

任务&#xff1a;用 NeRF 对无线信号的传播进行建模&#xff0c;建模完成后可以用NeRF网络生成新位置下的信号。生成的信号用于指纹定位、信道估计等下游任务。 核心思路 在视觉 NeRF 的基础上&#xff0c;根据无线信号的特点修改了隐式场模型、渲染函数&#xff0c;网络的输…...

以太网交换安全:MAC地址表安全

一、MAC地址表安全 MAC地址表安全是网络安全中的一个重要方面&#xff0c;它涉及到网络设备的MAC地址表的管理和保护。以下是对MAC地址表安全的详细介绍&#xff1a; &#xff08;1&#xff09;基本概念 定义&#xff1a;MAC地址表是网络设备&#xff08;如交换机&#xff0…...

CSS综合页布面局案例

写的比较一般,如果想要参考可以点击链接。 CSS综合案例(登录页面)资源-CSDN文库 引言: 我们学习CSS和HTML都是为了想要做一个网页布局,但是每逢上手可能就会需要查阅很多语言,我觉得是没有什么问题的,熟能生巧,编程是需要练的,但是写网页的时候需要实现某个效果时需…...

低代码可视化-UniApp二维码可视化-代码生成器

市面上提供了各种各样的二维码组件&#xff0c;做了一简单的uniapp二维码组件&#xff0c;二维码实现依赖davidshimjs/qrcodejs。 组件特点 跨浏览器支持&#xff1a;利用Canvas元素实现二维码的跨浏览器兼容性&#xff0c;兼容微信小程序、h5、app。 无依赖性&#xff1a;QR…...

Electron 使用 Nodemon 配置自动重启

在Electron项目中&#xff0c;每次修改了代码都需要手动关闭应用&#xff0c;再执行npm start重启应用。 Nodemon 是一个非常实用的工具&#xff0c;主要用于在开发 Node.js 应用时自动监测文件的变化并重新启动服务器。 安装nodemon 开发环境安装nodemon&#xff1a; npm …...

JVM和GC监控技术

一、监控技术简介 JVM是什么&#xff1f;项目里面有JVM吗&#xff1f;JVM跟Tomcat有什么关系&#xff1f;为什么需要去分析JVM&#xff1f; 1. JVM(全称&#xff1a;Java Virtual Machine)&#xff0c;Java虚拟机 是Java程序运行的环境&#xff0c;它是一个虚构的计算机&…...

Android中级控件

文章目录 图形定制1.图形Drawable2.形状图像3.九宫格图片4.状态列表图形 选择按钮1.复选框CheckBox2.开关按钮Switch3.单选按钮 文本输入1.编辑框EditText2.焦点变更监听器3.文本变化监听器 对话框1.提醒对话框2.日期对话框DatePickerDialog3.时间对话框TimePickerDialog 图形定…...

WebSocket消息防丢ACK和心跳机制对信息安全性的作用及实现方法

WebSocket消息防丢ACK和心跳机制对信息安全性的作用及实现方法 在现代即时通讯&#xff08;IM&#xff09;系统和实时通信应用中&#xff0c;WebSocket作为一种高效的双向通信协议&#xff0c;得到了广泛应用。然而&#xff0c;在实际使用中&#xff0c;如何确保消息的可靠传输…...

生信初学者教程(二十二):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 文件大小限制的问题&#xff0c;可以使用 Git Large File Storage (Git LFS)。以下是设置步骤&#xff1a; 安装 Git LFS&#xff1a; 对于 macOS&#xff1a;brew install git-lfs对于 Windows&#xff1a;从 Git LFS官网 下载并安装。 初始化 Git LFS&#xff…...

wordpress源码资源站整站打包32GB数据,含6.7W条资源数据

源码太大了&#xff0c;足足32gb&#xff0c;先分享给大家。新手建立资源站&#xff0c;直接用这个代码部署一下&#xff0c;数据就够用了。辅助简单做下seo&#xff0c;一个新站就OK了。 温馨提示&#xff1a;必须按照顺序安装 代码下载...

Cesium1.95中高性能加载1500个点

一、基本方式&#xff1a; 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...

Pinocchio 库详解及其在足式机器人上的应用

Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库&#xff0c;专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性&#xff0c;并提供了一个通用的框架&…...

AI病理诊断七剑下天山,医疗未来触手可及

一、病理诊断困局&#xff1a;刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断"&#xff0c;医生需通过显微镜观察组织切片&#xff0c;在细胞迷宫中捕捉癌变信号。某省病理质控报告显示&#xff0c;基层医院误诊率达12%-15%&#xff0c;专家会诊…...

在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案

这个问题我看其他博主也写了&#xff0c;要么要会员、要么写的乱七八糟。这里我整理一下&#xff0c;把问题说清楚并且给出代码&#xff0c;拿去用就行&#xff0c;照着葫芦画瓢。 问题 在继承QWebEngineView后&#xff0c;重写mousePressEvent或event函数无法捕获鼠标按下事…...

MyBatis中关于缓存的理解

MyBatis缓存 MyBatis系统当中默认定义两级缓存&#xff1a;一级缓存、二级缓存 默认情况下&#xff0c;只有一级缓存开启&#xff08;sqlSession级别的缓存&#xff09;二级缓存需要手动开启配置&#xff0c;需要局域namespace级别的缓存 一级缓存&#xff08;本地缓存&#…...

Python实现简单音频数据压缩与解压算法

Python实现简单音频数据压缩与解压算法 引言 在音频数据处理中&#xff0c;压缩算法是降低存储成本和传输效率的关键技术。Python作为一门灵活且功能强大的编程语言&#xff0c;提供了丰富的库和工具来实现音频数据的压缩与解压。本文将通过一个简单的音频数据压缩与解压算法…...

前端高频面试题2:浏览器/计算机网络

本专栏相关链接 前端高频面试题1&#xff1a;HTML/CSS 前端高频面试题2&#xff1a;浏览器/计算机网络 前端高频面试题3&#xff1a;JavaScript 1.什么是强缓存、协商缓存&#xff1f; 强缓存&#xff1a; 当浏览器请求资源时&#xff0c;首先检查本地缓存是否命中。如果命…...

DAY 26 函数专题1

函数定义与参数知识点回顾&#xff1a;1. 函数的定义2. 变量作用域&#xff1a;局部变量和全局变量3. 函数的参数类型&#xff1a;位置参数、默认参数、不定参数4. 传递参数的手段&#xff1a;关键词参数5 题目1&#xff1a;计算圆的面积 任务&#xff1a; 编写一…...

数据库正常,但后端收不到数据原因及解决

从代码和日志来看&#xff0c;后端SQL查询确实返回了数据&#xff0c;但最终user对象却为null。这表明查询结果没有正确映射到User对象上。 在前后端分离&#xff0c;并且ai辅助开发的时候&#xff0c;很容易出现前后端变量名不一致情况&#xff0c;还不报错&#xff0c;只是单…...

结构化文件管理实战:实现目录自动创建与归类

手动操作容易因疲劳或疏忽导致命名错误、路径混乱等问题&#xff0c;进而引发后续程序异常。使用工具进行标准化操作&#xff0c;能有效降低出错概率。 需要快速整理大量文件的技术用户而言&#xff0c;这款工具提供了一种轻便高效的解决方案。程序体积仅有 156KB&#xff0c;…...