【C语言进阶(11)】动态内存管理

news2024/10/6 20:34:40

文章目录

  • Ⅰ 存在动态内存分配的原因
  • Ⅱ 动态内存函数
    • 1. malloc
    • 2. calloc
    • 3. realloc
    • 4. free (重要)
  • Ⅲ 常见动态内存错误
    • 1. 对 NULL 指针的解引用操作
    • 2. 对动态开辟空间的越界访问
    • 3. 对非动态开辟内存使用 free 释放
    • 4. 使用 free 释放一块动态开辟内存的一部分
    • 5. 对同一块动态内存多次释放
    • 6. 动态开辟内存没有释放(内存泄漏)
  • Ⅳ 柔性数组

引用头文件

stdlib.h

Ⅰ 存在动态内存分配的原因

内存使用的方式

  1. 创建变量(开辟一块独立的空间)

    • 局部变量 -> 栈区。
    • 全局变量 -> 静态区。
  2. 创建数组(开辟一块连续的 空间)

    • 局部数组 -> 栈区。
    • 全局数组 -> 静态区。

上述开辟空间的方式有两个特点

  1. 空间开辟的大小是固定的
  2. 数组在定义的时候,必须指定数组的长度,它所需要的内存在编译时分配。

存在的问题

  • 不能灵活的开辟空间。如果需要的空间很少,但是固定开辟的空间非常大,就会造成内存浪费。如果需要的空间很多,固定开辟的空间很小,就会造成内存不够用的情况。
  • 因此就出现了动态内存函数,用来随时更改所开辟的空间的大小。

Ⅱ 动态内存函数

1. malloc

函数原型

void* malloc (size_t size);

函数功能

  • 开辟一块 size 个字节大小连续可用的空间,并返回该空间的起始地址。

函数返回值

  • 如果开辟成功,则返回一个指向开辟好空间的起始地址的指针。
  • 如果开辟失败,则返回一个 NULL 指针。因此要检查好 malloc 的返回值。
  • 返回值的类型是 void*,在使用指向开辟好的空间的起始地址的指针时,还需要转换成自己需要的指针类型。

函数用例

  • 申请一块空间,用来存放 10 个整形。
#include <stdlib.h>

int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	//malloc 返回的是 void* 的指针,需要转换成对应的指针类型才能使用

	return 0;
}

2. calloc

函数原型

void* calloc (size_t num, size_t size);

函数功能

  • 为 num 个大小为 size 的元素开辟一块空间,并且把该空间的每个字节初始化为 0。
  • 与 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。

函数用例

  • 开辟一块具有 10 个元素,每个元素 4 个字节的空间。

在这里插入图片描述

3. realloc

函数原型

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

函数功能

  • 调整由 ptr 所指向的空间大小为 size 个字节。

函数参数

  • ptr:指向由 malloc、calloc 所开辟的空间。
  • size:新空间的大小(字节)。

realloc 函数的两种使用情况

void* realloc (void* ptr, size_t size);
  1. 将 ptr 所指向的空间大小重新调整为 size 个字节。
void* realloc(NULL,size_t size);
  1. 开辟一块有 size 个字节的空间。

函数用例

在这里插入图片描述

realloc 调整空间的方式

realloc在调整内存空间时存在两种情况

  1. 情况 1:原有空间之后足够大的空间,扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化,返回旧地址

在这里插入图片描述

  1. 情况 2:原有空间之后没有足够大的空间,realloc 会重新找一个内存区域开辟一块符合要求的空间,返回新地址,拷贝旧数据,释放旧空间

在这里插入图片描述

4. free (重要)

函数原型

void free (void* ptr);

函数参数

  • ptr:指向先前用 malloc、calloc、realloc 分配的内存块的起始地址。

函数功能

  • 释放由 ptr 指向的动态开辟的空间。

函数用例

  • 释放由各个动态内存函数所开辟的空间。
int main()
{
	int* p1 = (int*)malloc(10 * sizeof(int));
	int* p2 = (int*)calloc(10, sizeof(int));
	int* p3 = (int*)realloc(NULL, sizeof(int) * 10);
	
	//......
	
	free(p1);
	free(p2);
	free(p3);

	return 0;
}

注意事项

  • free 会将指针指向的空间释放掉,但是不会改变指针内存放的地址值。
  • 也就是说,即使 free 掉了 p1 p2 p3 三个指针所指向的空间,这三个指针依然记得原先空间的起始地址。
  • 因此释放掉指针所指向的空间之后,还必须要将该指针变量的值置为 NULL。
int main()
{
	int* ptr = (int*)malloc(10 * sizeof(int));

	//......

	free(ptr);	//释放掉ptr指向的空间后,ptr内存的地址值未变,还是记得原来空间的地址
	ptr = NULL;	//因此要主动修改ptr内存放的地址值为NULL
	
	return 0;
}

Ⅲ 常见动态内存错误

1. 对 NULL 指针的解引用操作

  • 在使用之前未判断动态内存函数的返回值是否是 NULL,如果内存开辟失败返回的是 NULL,就成了对 NULL 指针的解引用操作。
int* p = (int*)malloc(40);
*p = 20;//使用前为判断指针 p 内的值是否有效

正确做法

int* p = (int*)malloc(40);

if (NULL == p)	//使用之前先判断该空间是否开辟成功
{
	return 1;	//1 为异常返回
}

*p = 20;

2. 对动态开辟空间的越界访问

  • 动态申请的空间也有着自己的范围,不能无限制的使用。
int* p = calloc(10, sizeof(int));

if (NULL == p)
{
	perror("calloc");
	return 1;
}
	
for (int i = 0; i <= 10; i++)//当 i 到 10 时越界访问
{
	printf("%d ", *(p + i));
}
free(p);
p = NULL;

3. 对非动态开辟内存使用 free 释放

  • free 只能释放由 malloc、calloc、realloc 开辟的空间。

在这里插入图片描述

4. 使用 free 释放一块动态开辟内存的一部分

  • 指向开辟空间起始地址的指针产生变动,没有从动态开辟内存的起始地址开始释放空间。

在这里插入图片描述

5. 对同一块动态内存多次释放

  • 忘记了已经释放过动态开辟的空间,又重新释放了一遍。
int* p = (int*)malloc(10 * sizeof(int));
//......
free(p);//此时已经释放过该空间一次
//.....
free(p);//睡蒙了又释放一次

解决办法

  • 在释放完动态开辟好的内存后,及时将该指针置为 NULL。之后如果再重复释放也不会产生问题。

6. 动态开辟内存没有释放(内存泄漏)

  • 只记得申请空间,不记得还。
void test()
{
	int* p = (int*)malloc(10 * sizeof(int));
	//p 是局部变量,出了作用域就销毁,等这个函数一结束就没人再记得这块空间的起始地址了

	if (true)//此时动态开辟的空间永远没机会释放了
	{
		return;
	}

	free(p);
	p = NULL;
}

int main()
{
	test();
	return 0;
}

Ⅳ 柔性数组

  • C99 中,结构中的最后一个元素允许是未知大小数组,这就叫做『柔性数组』成员。
struct S
{
	char c;
	int i;
	int arr[];	//未知大小数组 - 柔性数组成员
};

柔性数组的特点

  • 结构体中的柔性数组成员前面必须至少有一个其他成员。
  • sizeof 返回的这种结构体大小不包含柔性数组成员的大小。
  • 包含柔性数组成员的结构体用 malloc 函数进行内存的动态分配,并且分配的内存应该大于结构体的大小,以适应柔性数组的预期大小。
struct S
{
	char c;
	int i;
	int arr[];
};

int main()
{
	struct S* ps = (struct S*)malloc(sizeof(struct S) + 20);
	//这 20 个字节才是分配给柔性数组 arr 的

	return 0;
}

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

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

相关文章

基于SpringBoot的小区物业管理系统

基于SpringBoot的小区物业管理系统的设计与实现 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBootMyBatis工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 首页 管理员界面 摘要 基于SpringBoot的小区物业管理系统是一款为小区物业管理提…

华硕平板k013me176cx线刷方法

1.下载adb刷机工具, 或者刷机精灵 2.下载刷机rom包 华硕asus k013 me176cx rom固件刷机包-CSDN博客 3.平板进入刷机界面 进入方法参考&#xff1a; ASUS (k013) ME176CX不进入系统恢复出厂设置的方法-CSDN博客 4.解压ME176C-CN-3_2_23_182.zip&#xff0c;把UL-K013-CN-3.2.…

玩转快速排序(C语言版)

W...Y的主页 &#x1f60a; 代码仓库分享 &#x1f495; &#x1f354;前言&#xff1a; 本篇文章&#xff0c;我们来讲解一下神秘的快速排序。对于快速排序我相信大家都已经有所耳闻&#xff0c;但是快速排序是有很多的版本的。我们这次的目的就是快排的所有内容搞懂&#…

vs2015 设置字体

Source Code Pro一款堪称完美的编程字体_source code字体-CSDN博客

zkPoT:基于机器学习模型训练的ZKP

1. 引言 Sanjam Garg等人2023年论文 Experimenting with Zero-Knowledge Proofs of Training 中&#xff0c;所设计的zkPoT&#xff08;zero-knowledge proof of training&#xff09;协议&#xff1a; 为streaming-friendly的。所需RAM与训练电路size不呈比例。结合了MPC-in…

Kaggle - LLM Science Exam上:赛事概述、数据收集、BERT Baseline

文章目录 一、赛事概述1.1 OpenBookQA Dataset1.2 比赛背景1.3 评估方法和代码要求1.4 比赛数据集1.5 优秀notebook 二、BERT Baseline2.1 数据预处理2.2 定义data_collator2.3 加载模型&#xff0c;配置trainer并训练2.4 预测结果并提交2.5 相关优化 前言&#xff1a;国庆期间…

基于Java的手机在线商城设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

竞赛 机器视觉人体跌倒检测系统 - opencv python

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 机器视觉人体跌倒检测系统 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&…

【软考】4.3 模式分解/事务并发/封锁协议

《模式分解》 保持函数依赖分解 无损分解 分解后可还原关系模式充分必要条件&#xff1a;R1∩R2 —>&#xff08;R1 - R2&#xff09; || R1∩R2—>&#xff08;R2 - R1&#xff09;“R1 - R2” 即在R1筛选除了R2的元素 《并发控制》 事务的四种特性&#xff1a;原子…

UE4.27.2 自定义 PrimitiveComponent 出现的问题

目录 CreatePrimitiveUniformBufferImmediateFLocalVertexFactory 默认构造函数GetTypeHashENQUEUE_RENDER_COMMANDnull resource entry in uniform buffer parameters FLocalVertexFactory 在看大象无形&#xff0c;其中关于静态物体网络绘制的代码出错的 bug 我也搞了一会………

RabbitMQ-发布订阅模式和路由模式

接上文 RabbitMQ-工作队列 1 发布订阅模式 将之前的配置类内容都替换掉 Bean("fanoutExchange")public Exchange exchange(){//注意这里是fanoutExchangereturn ExchangeBuilder.fanoutExchange("amq.fanout").build();}Bean("yydsQueue1")publ…

全栈开发笔记2:项目部署上线的三种方式

文章目录 最原始的方式宝塔Docker 部署其他 本文为编程导航实战项目学习笔记。 项目部署的三种方式&#xff1a; 最原始方式✅ yum 手动安装 jdk mysql tomcat nginx打包前端项目&#xff0c;放到某个目录&#xff0c;修改 nginx 配置修改线上的 mysql 配置&#xff0c;打包 j…

MySQL 多表关联查询优化实践和原理解析

目录 一、前言二、表数据准备三、表关联查询原理和两种算法3.1、研究关联查询算法必备知识点3.2、嵌套循环连接 Nested-Loop Join(NLJ) 算法3.3、基于块的嵌套循环连接 Block Nested-Loop Join(BNL)算法3.4、被驱动表的关联字段没索引为什么要选择使用 BNL 算法而不使用 Nested…

C++_pen_静态与常量

成员 常成员、常对象&#xff08;C推荐使用 const 而不用#define,mutable&#xff09; const 数据成员只在某个对象生存周期内是常量&#xff0c;而对于整个类而言却是可变的&#xff08;static除外&#xff09; 1.常数据成员&#xff08;构造函数初始化表赋值&#xff09; c…

华为云云耀云服务器L实例评测|安装搭建学生成绩管理系统

1.前言概述 华为云耀云服务器L实例是新一代开箱即用、面向中小企业和开发者打造的全新轻量应用云服务器。多种产品规格&#xff0c;满足您对成本、性能及技术创新的诉求。云耀云服务器L实例提供丰富严选的应用镜像&#xff0c;实现应用一键部署&#xff0c;助力客户便捷高效的在…

【重拾C语言】四、循环程序设计典例整理(最大公因数、阶乘求和、正整数翻译、打印字符方阵、斐波那契数列……)

目录 前言 四、循环程序设计 4.3 程序设计实例 4.3.1 求两数最大公因数 4.3.2 阶乘求和 4.3.3 正整数翻译 4.3.4 打印字符方阵 4.3.5 百钱百鸡问题 4.3.6 斐波那契数列 4.3.7 迭代法解方程 前言 ChatGPT C语言是一种通用的、过程式的计算机编程语言&#xff0c;由贝…

SQL与关系数据库基本操作

SQL与关系数据库基本操作 文章目录 第一节 SQL概述一、SQL的发展二、SQL的特点三、SQL的组成 第二节 MySQL预备知识一、MySQL使用基础二、MySQL中的SQL1、常量&#xff08;1&#xff09;字符串常量&#xff08;2&#xff09;数值常量&#xff08;3&#xff09;十六进制常量&…

JAVA学习(3)-全网最详细~

回顾 昨天学了 Java 中的数据类型-整型 int - integer,以及什么是标识符identifier和它的命名规则,什么是保留字(reserved word key word),最后还谈到了Java变量包括局部变量和成员变量(在类内部,方法外部的变量),变量必须要初始化,否则会报错.如果有遗忘或者是感兴趣的小伙伴…

ESLint自动修复代码规范错误

基于 vscode 插件 ESLint 高亮错误&#xff0c;并通过配置 自动 帮助我们修复错误 在设置中 settings.json添加这段代码就自动修复错误 // 当保存的时候&#xff0c;eslint自动帮我们修复错误 "editor.codeActionsOnSave": { "source.fixAll": true }, /…

免费app签名分发平台应用cdn分发平台为什么会免费?商业分析他的盈利模式

近年来&#xff0c;随着移动应用的迅速发展&#xff0c;免费app签名分发平台和应用CDN分发平台日益受到开发者和用户的关注。本报告旨在分析这些平台的商业模式&#xff0c;探讨其利润点、营销点以及所采取的优势。 一、商业模式分析&#xff1a; 广告收入&#xff1a; 免费a…