C语言--动态内存管理1

目录

  • 前言
  • 动态内存函数介绍
    • malloc
    • free
    • calloc
    • realloc
  • 常见的动态内存错误
    • 对NULL指针的解引用操作
    • 对动态开辟空间的越界访问
    • 对非动态开辟内存使用free释放
    • 使用free释放一块动态开辟内存的一部分
    • 对同一块动态内存多次释放
    • 动态开辟内存忘记释放(内存泄漏)
  • 对通讯录进行优化

前言

我们目前已知的内存开辟方式是要指定长度的,比如以下这两种方式

int val = 20;
char arr[10] = {0};

我们发现:

  1. 这些空间开辟的大小时固定的,无法被修改
  2. 如果是数组,在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配.

再比如我们的通讯录,如果我们一开始就确定了人员个数的最大值,那么一旦到达这个上限,就无法继续录入数据了,我们能不能实现一种能根据我们的需求灵活更改空间大小的方式呢?这时候我们就需要动态内存开辟空间了。

动态内存函数介绍

malloc

我们先来看书写的格式:

     void* malloc (size_t size);

malloc本质上是一个函数,它的作用是开辟内存块,它有参数,有返回值,我们现在来看看分别是什么:

1. 如果开辟空间成功,则返回一个指向开辟好空间的指针

2. 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定
因为malloc这个函数的返回值是void类型的指针,所以我们在接收的时候,要强制力类型转换为我们想要的类型,并用相应的指针来接收。比如我们要申请5个整型*的内存空间,代码就应这样书写:

     int* p=(int)malloc(20);//注意这里的20是指20个字节,即5个整型大小

值得注意的是,此时我们申请的动态内存空间是在堆区的,这有别于我们之前在栈区开辟空间。

3. 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查
我们知道,对空指针解引用操作是很危险的,所以我们一定要在使用前检查一下,代码如下

//申请
	int* p = (int*)malloc(20);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}

4. 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器

free

C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的.
书写格式如下:

void free (void* ptr);
  1. 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
  2. 如果参数 ptr 是NULL指针,则函数什么事都不做
    我们将 p free掉后看一下p的地址是否发生改变。
    图1
    我们发现,虽然空间被回收了,但是p内存放的地址并没有改变,这时候如果再对其解引用便是非法访问了,所以我们要手动将p的值赋为NULL,具体代码实现如下
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>

int main()
{
	int* p = (int*)malloc(20);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	free(p);
	p = NULL;
	return 0;
}

备注:mallocfree都声明在 stdlib.h 头文件中。

calloc

C语言还提供了一个函数叫 calloc , calloc 函数也用来动态内存分配。原型如下:

void* calloc (size_t num, size_t size);
  1. 函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0
  2. 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的**每个字节初始化为全0

假设我们要申请十个int类型大小的空间,书写方式如下

int *p = (int*)calloc(10, sizeof(int));

realloc

我们看这个函数的前缀re,联系英语常识可以大概猜到这个函数的用途是重新,再次开辟一块空间。
有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时
候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小
的调整

书写格式如下

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

1. ptr要调整的内存地址
2. size调整之后新大小
3. 返回值调整之后的内存起始位置

要注意区分的是:realloc在调整内存空间的是存在两种不同情况,分别是原有空间之后有足够大的空间原有空间之后没有足够大的空间

情况1: 原有空间之后有足够大的空间
图2
此时,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。

情况2: 原有空间之后没有足够大的空间
图3
此时,realloc会找更大的空间(找不到就开辟失败了),将原来的数据拷贝到新的空间去,释放旧的空间,返回新空间的地址。

常见的动态内存错误

对NULL指针的解引用操作

我们来看下面这段代码

void test()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20;
free(p);
}

我们在前面讲到过,如果malloc函数开辟空间失败,就会返回空指针,对空指针解引用很明显存在问题。

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

void test()
{
int i = 0;
int *p = (int *)malloc(10*sizeof(int));
if(NULL == p)
{
exit(EXIT_FAILURE);
}
for(i=0; i<=10; i++)
{
*(p+i) = i;
}
free(p);
}

上面这段代码当i=10的时候,已经超出我们开辟的空间范围了,这时候再解引用就会越界访问

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

void test()
{
int a = 10;
int *p = &a;
free(p);
}

我们前面说过: 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。所以这里我们对非动态开辟内存使用free释放肯定是行不通的

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

void test()
{
int *p = (int *)malloc(100);
p++;
free(p);
}

看这段代码我们发现,p++使得p不再指向动态内存开辟的起始位置,这样写是有问题的。

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

void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);
}

这样写是有问题的,但是如果我们释放完一次就将p赋值为空指针,这时候再对p free就不会有任何问题了。

void test()
{
int *p = (int *)malloc(100);
free(p);
p=NULL;
free(p);
}

我们在前面也讲过,如果参数 ptr 是NULL指针,则函数什么事都不做

动态开辟内存忘记释放(内存泄漏)

void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while(1);
}

我们首先要了解一点的是:以上的所有函数所申请的空间,如果不想使用,需要free释放,如果不想使用free释放,在程序结束之后,也会由操作系统回收
但是,如果不使用free释放,程序也不结束,就会造成内存泄露

这里尤其要注意的点是,如果动态内存函数开辟空间是在另一个函数内部进行的,出了这个函数,这块空间是没有像局部变量一样被回收的,只有free能将它回收!!!

对通讯录进行优化

在学习了动态内存函数后,我们可以将其灵活运用到通讯录中,实现可以对达到人数上限的通讯录进行扩容。
主要修改点在于
1,初始化
我们假定初始大小为3,每次扩容大小为2。这里我们可以用,方便修改。
同时,因为我们需要对当前容量与最大容量比较决定是否扩容,所以还需一个参数capacity来存放最大容量的的数值。

#define DEFAULT_SZ 3
#define INC_SZ 2

void InitContact(Contact* pc)
{
	pc->sz = 0;
	pc->data = malloc(DEFAULT_SZ * sizeof(PeoInfo));
	if (pc->data == NULL)
	{
		printf("通讯录初始化失败:%s", strerror(errno));
		return;
	}
	pc->sz = 0;
	pc->capacity = DEFAULT_SZ;
}

2,扩容

void CheckCapacity(Contact* pc)
{
	if (pc->sz == pc->capacity)//判断是否达到容量上限
	{
		PeoInfo* ptr = realloc(pc->data, (pc->capacity + INC_SZ) * sizeof(PeoInfo));
		if (ptr == NULL)
		{
			printf("扩容失败:%s\n",strerror(errno));
			return;
		}
		else
		{
			pc->data = ptr;
			pc->capacity += INC_SZ;
			printf("扩容成功,当前容量:%d\n", pc->capacity);
		}
	}
}

3,销毁

void DestroyContact(Contact* pc)
{
	free(pc->data);
	pc->data = NULL;
	pc->capacity = 0;
	pc->sz = 0;
}

以上就是本章全部内容,如有出入,欢迎指正。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

TCP和UDP协议的区别?

是否面向连接&#xff1a; TCP 是面向连接的传输&#xff0c;UDP 是面向无连接的传输。 是否是可靠传输&#xff1a;TCP是可靠的传输服务&#xff0c;在传递数据之前&#xff0c;会有三次握手来建立连接&#xff1b;在数据传递时&#xff0c;有确认、窗口、重传、拥塞控制机制…

Linux编辑器-vim

一、vim简述1&#xff09;vi/vim2&#xff09;检查vim是否安装2)如何用vim打开文件3)vim的几种模式命令模式插入模式末行模式可视化模式二、vim的基本操作1)进入vim&#xff08;命令行模式&#xff09;2)[命令行模式]切换至[插入模式]3)[插入模式]切换至[命令行模式]4)[命令行模…

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

真正的人生&#xff0c;只有在经过艰难卓绝的斗争之后才能实现。 ——塞涅卡 目录 一.为什么存在动态内存分配&#xff1f; 二.动态内存管理的函数 1.malloc函数 2.free函数 ​3.calloc函数 4.realloc函数 三.常见的动态内存错误 1.对N…

python编程:使用pyecharts绘制拟合曲线图

pyecharts库是python下实现的echarts图表绘制库&#xff0c;接下来&#xff0c;我们使用pyecharts来绘制一条曲线&#xff0c;来体验一下pyecharts的基本使用效果。 1、首先&#xff0c;我们要安装下pyecharts库&#xff0c;在pycharm终端输入安装命令&#xff1a; pip install…

pytorch实现深度神经网络与训练

目录 1. 随机梯度下降算法 2.优化器 3. 损失函数 3.1 均方误差损失 3.2 交叉熵损失 4.防止过拟合 4.1 过拟合的概念 4.2 防止过拟合的方法 5. 网络参数初始化 5.1 网络参数初始化方法 5.2 参数初始化方法应用实例 1.针对某一层的权重进行初始化 2.针对一个网络的权…

基于ESP32做低功耗墨水屏时钟

基于ESP32做低功耗墨水屏时钟电子墨水屏概述ESP32实验低功耗电子时钟功能描述接线开发实验结果电子墨水屏 概述 电子墨水是一种革新信息显示的新方法和技术。和传统纸差异是电子墨水在通电时改变颜色&#xff0c;并且可以显示变化的图象&#xff0c;像计算器或手机那样的显示。…

使用ArcGIS为科研论文制作正确、美观、详细的插图

科研论文中的插图&#xff0c;如果图中包含地理信息&#xff0c;那么首先需要在图中标明指北针、比例尺、图例&#xff0c;然后在此基础上再对作的图进一步的美化和修改。 来源&#xff1a;https://doi.org/10.1016/j.uclim.2022.101326 这种就是属于是最常见的研究区概况图&a…

(只需五步)注册谷歌账号详细步骤,解决“此电话号码无法验证”问题

目录 第一步&#xff1a;打开google浏览器 第二步&#xff1a;设置语言为英语&#xff08;美国&#xff09; 第三步&#xff1a;点击重新启动&#xff0c;重启浏览器 第四步&#xff1a;开始注册 第五步&#xff0c;成功登录google账号&#xff01; 如果出现这样的原因&…

java多线程之线程安全(重点,难点)

线程安全1. 线程不安全的原因:1.1 抢占式执行1.2 多个线程修改同一个变量1.3 修改操作不是原子的锁(synchronized)1.一个锁对应一个锁对象.2.多个锁对应一个锁对象.2.多个锁对应多个锁对象.4. 找出代码错误5. 锁的另一种用法1.4 内存可见性解决内存可见性引发的线程安全问题(vo…

乐观锁和悲观锁 面试题

Mysql的乐观锁和悲观锁 实现方式加锁时机常见的调用方式优势不足适用场景乐观锁开发自定义更新数据的时候sql语句中进行version的判断高并发容易出现不一致的问题高并发读&#xff0c;少写悲观锁Mysql内置查询数据的开始select * for update保证一致性低并发互联网高并发场景极…

linux实验之shell编程基础

这世间&#xff0c;青山灼灼&#xff0c;星光杳杳&#xff0c;秋风渐渐&#xff0c;晚风慢慢 shell编程基础熟悉shell编程的有关机制&#xff0c;如标准流。学习Linux环境变量设置文件及其内容/etc/profile/etc/bashrc/etc/environment~/.profile~/.bashrc熟悉编程有关基础命令…

JVM类加载机制

文章目录定义类加载过程加载链接验证准备解析初始化类加载器双亲委派模型定义 Java 虚拟机把描述类的数据从 Class 文件加载到内存&#xff0c;并对数据进行校验、转换解析和初始化&#xff0c;最终形成可以被虚拟机直接使用的 Java 类型&#xff0c;这个过程被称为虚拟机的类…

有手就行 -- 搭建图床(PicGo+腾讯云)

&#x1f373;作者&#xff1a;贤蛋大眼萌&#xff0c;一名很普通但不想普通的程序媛\color{#FF0000}{贤蛋 大眼萌 &#xff0c;一名很普通但不想普通的程序媛}贤蛋大眼萌&#xff0c;一名很普通但不想普通的程序媛&#x1f933; &#x1f64a;语录&#xff1a;多一些不为什么的…

2023最新最详细【接口测试总结】

序章 ​ 说起接口测试&#xff0c;网上有很多例子&#xff0c;但是当初做为新手的我来说&#xff0c;看了不不知道他们说的什么&#xff0c;觉得接口测试&#xff0c;好高大上。认为学会了接口测试就能屌丝逆袭&#xff0c;走上人生巅峰&#xff0c;迎娶白富美。因此学了点开发…

嵌入式学习笔记——SysTick(系统滴答)

系统滴答前言SysTick概述SysTick是个啥SysTick结构框图1. 时钟选择2.计数器部分3.中断部分工作一个计数周期&#xff08;从重装载值减到0&#xff09;的最大延时时间工作流程SysTick寄存器1.控制和状态寄存器SysTick->CTRL2.重装载值寄存器SysTick->LOAD3.当前值寄存器Sy…

async与await异步编程

ECMA2017中新加入了两个关键字async与await 简单来说它们是基于promise之上的的语法糖&#xff0c;可以让异步操作更加地简单明了 首先我们需要用async关键字&#xff0c;将函数标记为异步函数 async function f() {} f()异步函数就是指&#xff1a;返回值为promise对象的函…

51单片机之喝水提醒器

定时器定时器介绍晶振晶体震荡器&#xff0c;又称数字电路的“心脏”&#xff0c;是各种电子产品里面必不可少的频率元器件。数字电路的所有工作都离不开时钟&#xff0c;晶振的好坏、晶振电路设计的好坏&#xff0c;会影响到整个系统的稳定性。时钟周期时钟周期也称为振荡周期…

数据库备份

数据库备份&#xff0c;恢复实操 策略一&#xff1a;&#xff08;文件系统备份工具 cp&#xff09;&#xff08;适合小型数据库&#xff0c;是最可靠的&#xff09; 1、停止MySQL服务器。 2、直接复制整个数据库目录。注意&#xff1a;使用这种方法最好还原到相同版本服务器中&…

银河麒麟v10sp2安装nginx

nginx官网下载&#xff1a;http://nginx.org/download/ 银河麒麟系统请先检查yum源是否配置&#xff0c;若没有配置请参考&#xff1a;https://qdhhkj.blog.csdn.net/article/details/129680789 一、安装 1、yum安装依赖 yum install gcc gcc-c make unzip pcre pcre-devel …

用嘴写代码?继ChatGPT和NewBing之后,微软又开始整活了,Github Copilot X!

用嘴写代码&#xff1f;继ChatGPT和NewBing之后&#xff0c;微软又开始整活了&#xff0c;Github Copilot X&#xff01; AI盛行的时代来临了&#xff0c;在这段时间&#xff0c;除了爆火的GPT3.5后&#xff0c;OpenAI发布了GPT4版本&#xff0c;同时微软也在Bing上开始加入了A…
最新文章