C语言-动态内存分配讲解

news2024/12/23 19:10:07

e6580e187d94491f9f9bdf87002464b8.jpeg

目录

✨1.什么是动态内存分配

💕2.动态内存开辟函数 malloc

✨3.malloc函数的检查(两种方法)

  💕4.动态内存释放函数 free

 ✨5.free 函数接收空指针

✨6.为什么要释放动态内存

💕7.动态内存开辟函数calloc

💕8.动态内存调整函数 realloc

✨9.动态内存分配——分配数组

9.1  动态内存分配——一维数组

9.2  动态内存分配——二维数组


春秋蝉在耳边鸣 无人与我并肩行        

永生路上多坎坷 这路一人也走的


  

✨1.什么是动态内存分配

我们在处理内存时,内存区域主要分为三块区域,分别为:栈区,堆区,静态区

而我们经常使用的语句其实都是在栈区开辟空间的,如:

int arr[10];
char str[20];

struct S s[10];

int a;

float b;

char c;

但是使用这样的声明,在栈区开辟空间时是有一定风险的

存在风险:

1. 开辟出来的空间是固定的,不能进行具体字节大小的修改,所以可能浪费掉栈区的空间大

2. 栈区空间开辟太大可能导致栈溢出

因此,我们可以将一些东西存储在堆区中,堆区的空间是本身就存在的,我们要做的就是分配出来并利用这些空间,因此也叫做动态内存分配

动态内存的分配是在堆区完成的,同时,堆区的内存空间几乎于无限大,因此不必担心如栈溢出的问题 


动态内存分配是需要调用动态内存函数实现的,下面介绍四种内存函数

点击超链接可以进行访问函数

💕2.动态内存开辟函数 malloc

动态内存开辟函数访问链接 malloc

#include<stdlib.h>
void* malloc(size_t size)
//以上是malloc函数的头文件以及参数

malloc 函数会返回一个void*类型的指针,这个指针是 malloc开辟空间大小的首地址

size表示空间大小是size个字节

 动态内存函数 malloc 可以在堆区开辟出一定的空间大小供程序员使用

1. malloc如果开辟成功,会返回所开辟空间的首地址

2. malloc如果开辟失败,就会返回空指针(NULL指针) 

我们无法确定malloc函数100%开辟成功,所以在利用malloc函数开辟空间时一定要进行检查

使用效果如下:

5f91c02d22f54fee933b278829890c55.png

在使用malloc函数开辟空间时,因为malloc函数返回的地址是void*类型,因此我们需要根据自己的实际需要,进行地址类型的转换 

如图:我们用malloc在堆区开辟了40个字节的内存空间大小,解引用p一次访问4个字节的大小 

但我们会发现,我们使用malloc函数开辟的空间大小,没有解引用的部分都不会初始化值为0,返回全部都是cd,这是因为malloc函数在开辟空间大小时不会初始化内容


但我们这样使用malloc函数其实是不对的,之前我们强调过,malloc函数的使用一定要进行检查,否则编译器就会出现警告,如下图:

421fe78d67604377b6429170eca88e64.png

那么我们该如何解决呢?


✨3.malloc函数的检查(两种方法)

我们使用完malloc函数开辟内存后,因为无法100%确定开辟成功,所以我们需要对malloc函数是否开辟成功进行检查

检查的两种方法:

1. 利用assert函数进行检查

2. 使用if语句进行检查

1.assert情况检查

#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#define NDEBUG
int main() {
	int* p = (int*)malloc(40);
	assert(p != NULL);
	*p = 20;
	printf("%d", *p);
}

malloc函数开辟失败就会返回空指针,因此只需要判断是否为空指针即可,如果为空指针,assert就会报错并停止运行代码(使用assert不用担心 NDEBUG 注释掉)

但使用assert有一点不好,如果我们再使用assert函数断言其他代码的同时,确定完malloc函数开辟成功后不能使用#define NDEBUG 注释掉assert函数,因为无法判断下一次执行代码malloc是否会开辟成功,这样就会影响代码的运行速度。可以使用assert函数,并不推荐用

2.if语句判断

#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
#include<stdlib.h>
int main() {
	int* p = (int*)malloc(40);
	if (p == NULL) {
		perror("malloc");
		return 1;
	}
	*p = 20;
	printf("%d", *p);
}

 使用 if 语句,只需要判断是否为空指针即可,如果为空指针,我们可以使用perror函数打印出错误信息,并及时结束代码返回1,推荐使用 if 语句


  💕4.动态内存释放函数 free

我们在堆区开辟完空间后,我们可以使用 free 函数释放掉开辟的内存,并将这块内存空间还给操作系统

动态内存释放函数访问 free

#include<stdlib.h>
void free(void* ptr)

free函数接收一个因动态内存函数所开辟内存空间的首地址

注意:

1. free函数接受的地址一定是动态内存在堆区开辟空间的地址,不能用栈区的地址

2.free函数所接受的地址一定是动态内存开辟堆区地址的首地址

以上两种情况,如果实现任意一种,代码直接崩

75536386ad974c5bb6485a6045810fb6.png45cbc587b02c482285a428a667e69812.png


 free函数正确示例如下:

free 释放前的内存

f86364fbe7d149a9b900991f319e82f8.png

free释放后的内存

fac472dfe03446119856fda64127c36d.png

注意:释放空间之后,free函数接收的指针(也就是因动态内存分配出的空间的首地址)就没有任何意义,但 free 不会自动给它置成空指针,此时他就成为一个野指针,所以我们要即及时将其置成空指针,如下:

#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
#include<stdlib.h>
int main() {
	int* p = (int*)malloc(40);

	if (p == NULL) {
		return 1;
	}

	free(p);
	p == NULL;
    //初始化为空指针
    //野指针在使用时是非常危险的,因为你不知道它指向哪里,所以要及时定义为空指针
}

 ✨5.free 函数接收空指针

free函数在接受空指针时会进行摆烂状态,什么也不做,如下:

650c5fba0426455d8a7c8d1637965746.png  


✨6.为什么要释放动态内存

在我们利用动态内存分配出来的空间后,这些分配出来的空间只有两种方法可以释放掉

1. 利用free函数,将动态内存分配出来的空间释放掉

2. 等待程序自己结束,程序结束时会将这些空间释放掉

所以及时的释放掉堆区内存是非常重要的


💕7.动态内存开辟函数calloc

calloc函数与malloc函数都为开辟动态内存的函数,两者极其相似却有不同,接下来进行讲解

动态内存开辟函数访问 calloc

#include<stdlib.h>
void* calloc (size_t num, size_t size);

calloc函数的第一个参数 num 为元素的个数

                    第二个参数 size 为每个元素字节的大小

calloc函数与malloc函数相同点:

如果开辟成功,会返回所开辟空间中较小空间的地址

如果开辟失败,就会返回空指针(NULL指针) 

我们无法确定calloc函数100%开辟成功,所以在利用malloc函数开辟空间时一定要进行检查

calloc函数与malloc函数不同点

1. 参数不同

2. calloc函数在开辟完内存空间后,会将内存空间中每个字节全部初始化为0

效果如下:

b87326281fd541e89ce49860eda3f32a.png

462d408589a24b4b969985a59756b400.png

可见,calloc与malloc函数相似,开辟的所有内存,每一个字节都会初始化为0

在free方面用法相同


💕8.动态内存调整函数 realloc

动态内存调整函数 realloc

虽然我们能通过 malloc 或 calloc 函数动态内存分配一块空间,但这块空间分配完成后大小也是固定的,如果空间满了需要扩容或空间多了需要缩减,这时 realloc 函数就登场了

realloc 的作用是对已经动态分配的一块空间再次分配。

#include<stdlib.h>
void* realloc (void* ptr, size_t size);

它有两个参数:

  1. ptr:要调整的内存地址,这块内存是动态内存分配得到的,必须是首地址,如果不是首地址代码运行就会崩
  2. size:以字节为单位的新大小

它会返回调整之后的内存起始位置。

关于调整之后的内存起始位置会出现以下两种情况:

 1. 与原来内存的起始位置相同
 2. 与原来内存的起始位置不同


当要缩小原有的内存时,说明原来的内存空间已经足够,此时它的返回值就是原来内存的起始位置。


当要扩大原有的内存时,又有两种情况:
1. 原有空间之后有足够大的内存时,直接在原内存的基础上再开辟后边几个连续的空间,此时它的返回值是原来内存的起始位置;
2. 原有空间之后没有足够大的内存进行扩容时,此时会在堆空间上另找一个合适大小的连续空间来使用,这样函数返回的是一个新的内存地址,与原来内存的起始位置不同。这时会拷贝原来空间的内容到新的空间的相应位置,原空间就被释放掉了。

虽然情况这么多,但其实很好更改与理解

所以,在使用 realloc 函数时就要注意用一个临时指针接收,当内存调整成功后再将临时指针赋给原指针

代码如下:

#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
#include<stdlib.h>
int main() {
	int* p = (int*)malloc(50);
	if (p == NULL) {
		return 1;
	}

	int * ptr = (int*)realloc(p, 30);

	if (ptr != NULL) {
		p = ptr;
	}

	free(p);
	p = NULL;
}

realloc函数调整前:

8fa86ce350bc496d9385cceb0e58aaf6.png

realloc函数调整后: 

a437c3be48324b8b9b10eecc070f1a83.png

realloc函数在有一种情况下等价于malloc

代码如下: 

#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
#include<stdlib.h>
int main() {
	int* p = (int *)realloc(NULL, 40);
	if (p == NULL) {
		return 1;
	}
	free(p);
	p = NULL;
}

这种情况下,就相当于开辟了40个字节的空间大小


✨9.动态内存分配——分配数组

9.1  动态内存分配——一维数组

利用动态内存分配出一维数组是较容易的,我们知道一维数组在内存中存储是连续的,我们可以利用这一点,动态内存分配出连续的空间,然后进行赋值利用,就实现了动态内存分配一维数组

代码如下:

#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
#include<stdlib.h>
int main() {
	int* p = (int*)malloc(40);

	if (p == NULL) {
		return 1;
	}

	for (int i = 0; i < 10; i++) {
		p[i] = i;
	}

	for (int i = 0; i < 10; i++) {
		printf("%d ", p[i]);
	}

	free(p);
	p = NULL;
}

可能不懂的点:

1. p[ i ] = *(p+i)


9.2  动态内存分配——二维数组

 使用动态内存分配出二维数组,我们有三种方法

第一种:利用二维数组其实是一堆一维数组的原理,开辟足够多的一维数组,并当作二维数组使用

d02a9b3a49e4460585c2cea05bba2042.png

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#define row 8
#define line 5
int main()
{
	
	int * p = (int*)malloc(row*line*sizeof(int));
	//或者int * p = (int*)malloc(160*sizeof(char));
    //开辟40个int类型的空间大小

	int count = 1;
	assert(p!=NULL);
	for(int i = 0;i<row;i++){

		for(int j = 0;j<line;j++){
			*(p+(i*line)+j) = count++;
		}
	}
	
		for(int i = 0;i<row;i++){
		for(int j = 0;j<line;j++){
			printf("%d ",	*(p+(i*line)+j));
		}
		printf("\n");
	}
	free(p);
	p = NULL;

}	

第二种:开辟4个 int* 类型的空间大小,让每一个 int* 都指向 5个int 的首地址 

 d2aad90a230e4e88a5558ecf7d43d764.png

#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
int main()
{
	int** p = (int**)malloc(4 * sizeof(int*));
	assert(p != NULL);
	for (int i = 0; i < 4; i++) {
		*(p + i) = (int*)malloc(5 * sizeof(int));
		assert(p + i != NULL);
	}
	int count = 1;
	for (int i = 0; i < 4; i++) {
		for (int j = 0; j < 5; j++) {
			assert((*(p + i)) + j != NULL);
			//*(*(p + i) + j * sizeof(char)) = count++;
			*((*(p + i)) + j) = count++;

		}
	}

	for (int i = 0; i < 4; i++) {
		for (int j = 0; j < 5; j++) {
			assert((*(p + i)) + j != NULL);
			//*(*(p + i) + j * sizeof(char)) = count++;
			printf("%d ", * ((*(p + i)) + j) );

		}
		printf("\n");
	}


	//释放每个int*所代表的5个int
	for (int i = 0; i < 4; i++) {
		free(*(p + i));
		*(p + i) = NULL;
	}

	//释放一级指针
	free(p);
	p = NULL;
}

这里一定要都释放,只释放 int* 的空间并不会释放 int 的空间

3e4fc21fb6644a9eaea23095a00d0f34.png

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

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

相关文章

免杀笔记 ---> 一种有想法的Indirect-Syscall

今天来分享一下&#xff0c;看到的一种Indirect-Syscall&#xff0c;也是两年前的项目了&#xff0c;但是也是能学到思路&#xff0c;从中也是能感受到杀软对抗之间的乐趣&#xff01;&#xff01;说到乐趣&#xff0c;让我想起看到过一位大佬的文章对"游褒禅山记"的…

学习RocketMQ

RocketMQ是一个分布式消息和流平台&#xff0c;它被设计为具有简单和可复制的架构&#xff0c;同时具有高性能和高可靠性。以下是RocketMQ从入门到精通的一些关键概念和示例代码。 1、安装和启动RocketMQ 1.1、下载并解压RocketMQ二进制文件。 wget https://archive.apache.…

JavaWeb--08BeanUtils:自定义转换器

在07创建了表单&#xff0c;但是获取网页信息的java代码太繁杂了&#xff0c;每次获取数据都要书写依次如下的&#xff1a; 重新创建一个web工程项目test1010---需要配置tomacat&#xff0c;具体传送门&#xff1a;CSDN 配置好了如下&#xff1a; 在里面web目录下创建一个reg…

JVM 基础知识(基础组成 )

使用场景 线上系统突然宕机,系统无法访问,甚至直接 O0M;线上系统响应速度太慢,优化系统性能过程中发现 CPU 占用过高,原因也许是因为 JVM的 GC 次数过于频繁;新项目上线,需要设置 JVM的各种参数;等 JDK / JRE / JVM JDK JDK 全称 ( Java Development Kit ) &#xff0c;是 Ja…

Git 使用方法

简介 Git常用命令 Git 全局设置 获取Git 仓库 方法二用的比较多 将仓库链接复制 在 git base here ----> git clone 仓库链接 工作区、暂存区、版本库 Git 工作区中文件中的状态 本地仓库的操作 远程仓库操作 git pull 将代码推送到远程仓库 1. git add 文件名 ---放…

C++:继承和多态,自定义封装栈,队列

1.栈&#xff1a; stack.cpp #include "stack.h"Stack::Stack():top(nullptr),len(0){} //析构函数 Stack::~Stack() {while(!empty()){pop();} }bool Stack::empty() //判断栈是否为空 {return topnullptr; }int Stack::size()//获取栈的大小 {return len; } //压…

万字长文详解Java线程知识

什么是进程、线程、协程&#xff1f; 进程 进程是计算机科学中的一个核心概念&#xff0c;它指的是在操作系统中正在执行的一个程序的实例。进程是操作系统中的一个独立执行单元&#xff0c;具有独立的内存空间和系统资源。每个进程都有自己独立的地址空间和文件描述符&#x…

循环神经网络笔记

循环神经网络学习 RNN训练方法–BPTT BPTT &#xff08;Backpropagation Through Time&#xff09;&#xff0c;这是一种用于训练循环神经网络&#xff08;RNNs&#xff09;的算法。由于 RNNs 能够处理序列数据&#xff0c;并且在每个时间步上都有内部状态&#xff0c;因此需…

南京自闭症寄宿学校:打造温馨的第二家

南京自闭症寄宿学校的愿景与广州星贝育园的温馨实践 在探讨自闭症儿童教育的广阔领域中&#xff0c;寄宿制学校以其独特的优势&#xff0c;为这些特殊的孩子提供了全方位的支持与关怀&#xff0c;致力于打造一个温馨如家的第二生活环境。虽然本文的主题是围绕南京自闭症寄宿学…

Chirp通过Sui让IoT世界变得更简单

据估计&#xff0c;未来十年内&#xff0c;联网设备的数量将增长到近400亿台。无论是追踪共享出行车辆的移动、改善食品追溯性、监控制造设施&#xff0c;还是保障家庭安全&#xff0c;物联网 ( Internet of Things&#xff0c;IoT) 对企业和消费者来说都已经成为一项关键技术。…

刷题学习日记 (1) - SWPUCTF

写这篇文章主要是想看看自己一个下午能干啥&#xff0c;不想老是浪费时间了&#xff0c;所以刷多少题我就会写多少题解&#xff0c;使用nss随机刷题&#xff0c;但是今天下午不知道为啥一刷都是SWPUCTF的。 [SWPUCTF 2021 新生赛]gift_F12 控制台ctrlf搜索flag即可&#xff0…

什么是竞争条件?

竞争条件&#xff0c;简单来说就是多个进程同时访问同一个共享资源&#xff0c;导致出现预期结果以外的错误的情况。 出现竞争条件的本质原因是cpu对程序的调度是没有特定规律的&#xff0c;某一时刻cpu处理哪个进程是不确定的。 简单写一个测试程序&#xff0c;先需要子进程和…

ubuntu安装emqx

目录 1.预先下载好emqx压缩包 2.使用tar命令解压 3.进入bin目录 5.放开访问端口18083 6.从通过ip地址访问emqx后台 7.默认用户名密码为admin/public 8.登录后台 9.资源包绑定在此博文可自取 1.预先下载好emqx压缩包 2.使用tar命令解压 sudo tar -xzvf emqx-5.0.8-el8-…

手机轻松解压 RAR 文件指南

手机通常不直接支持 RAR 文件打开&#xff0c;主要有以下几个原因。首先&#xff0c;手机操作系统的设计初衷并非为了处理各种复杂的压缩文件格式。 大多数手机内置的文件管理器主要侧重于管理手机内部存储和常见的文件类型&#xff0c;如图片、音频、视频等。对于像 RAR 这样…

【UR #1】外星人(dp思维技巧)

考虑去除后效性&#xff0c;常用方法排序状态可以直接以答案为状态来判断合法性考虑转移方向&#xff0c;向后转移&#xff0c;选与不选来定向答案 f[i][j]表示前i个数答案为j的方案数 不选i 则加上f[i][j] 的方案数 * &#xff08;n-i&#xff09;,ai可以在后面随便选。 选…

Python 课程20-Scikit-learn

前言 Scikit-learn 是 Python 中最流行的机器学习库之一&#xff0c;它提供了多种用于监督学习和无监督学习的算法。Scikit-learn 的特点是简单易用、模块化且具有高效的性能。无论是初学者还是专业开发者&#xff0c;都可以借助它进行快速原型设计和模型开发。 在本教程中&a…

为何专利对企业创新与竞争至关重要?

在当今这个技术飞速发展的时代&#xff0c;每一个创新的火花都可能成为推动行业进步的关键力量。然而&#xff0c;创新并非一蹴而就&#xff0c;它需要时间、资金与智慧的共同投入&#xff0c;更需要一套完善的保护机制来确保其成果不被轻易窃取或模仿。这一重任&#xff0c;便…

WebPage-Bootstrap框架(container类,container-fluid类,栅格系统)

1.Bootstrap Bootstrap为页面内容和栅格系统包裹了一个.container容器&#xff0c;框架预先定义类 1.1container类 响应式布局容器的宽度 手机-小于768px 宽度设置100%&#xff1b; 平板-大于等于768px 设置宽度为750px 桌面显示器-大于等于992px 设置宽度 970px 大屏幕显…

医院排班|医护人员排班系统|基于springboot医护人员排班系统设计与实现(源码+数据库+文档)

医护人员排班系统目录 目录 基于springboot医护人员排班系统设计与实现 一、前言 二、系统功能设计 三、系统实现 医护类型管理 排班类型管理 科室信息管理 医院信息管理 医护信息管理 四、数据库设计 1、实体ER图 2、具体的表设计如下所示&#xff1a; 五、核心代码…

“AI+Security”系列第3期(五):AI技术在网络安全领域的本地化应用与挑战

近日&#xff0c;由安全极客、Wisemodel 社区、InForSec 网络安全研究国际学术论坛和海升集团联合主办的“AI Security”系列第 3 期技术沙龙&#xff1a;“AI 安全智能体&#xff0c;重塑安全团队工作范式”活动顺利举行。此次活动吸引了线上线下超过千名观众参与。 在活动中…