C语言->动态内存管理

系列文章目录


文章目录


前言

 ✅作者简介:大家好,我是橘橙黄又青,一个想要与大家共同进步的男人😉😉

🍎个人主页:橘橙黄又青_C语言,函数,指针-CSDN博客

目的:学习malloc,free,calloc,realloc函数的使用。

1. 为什么要有动态内存分配

我们已经掌握的内存开辟⽅式有:

int val = 20;//在栈空间上开辟四个字节
char arr[10] = {0};//在栈空间上开辟10个字节的连续空间
但是上述的开辟空间的⽅式有两个特点:
空间开辟⼤⼩是固定的。
数组在申明的时候,必须指定数组的⻓度,数组空间⼀旦确定了⼤⼩不能调整。
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间⼤⼩在程序运⾏的时候才能知
道,那数组的编译时开辟空间的⽅式就不能满⾜了。
C语⾔引⼊了动态内存开辟,让程序员⾃⼰可以申请和释放空间,就⽐较灵活了。

2. mallocfree

2.1 malloc

C语⾔提供了⼀个动态内存开辟的函数:

void* malloc (size_t size);

这个函数向内存申请⼀块连续可⽤的空间,并返回指向这块空间的指针。  

如果开辟成功,则返回⼀个指向开辟好空间的指针。
如果开辟失败,则返回⼀个 NULL 指针,因此malloc的返回值⼀定要做检查。
返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使⽤的时候使⽤者⾃ ⼰来决定。
如果参数 size 为0,malloc的⾏为是标准是未定义的,取决于编译器

代码演示:

int* p = (int*)malloc(10 * sizeof(int))开辟10个整形空间
malloc(字节)

2.2 free

C语⾔提供了另外⼀个函数free,专⻔是⽤来做动态内存的释放和回收的,函数原型如下:
void free (void* ptr);
free函数⽤来释放动态开辟的内存注意:
如果参数 ptr 指向的空间不是动态开辟的,那free函数的⾏为是未定义的。
如果参数 ptr 是NULL指针,则函数什么事都不做。
malloc和free都声明在 stdlib.h 头⽂件中。

举个例⼦:

#include <stdio.h>
#include <stdlib.h>
int main()
{
     int num = 0;
     scanf("%d", &num);
     int arr[num] = {0};
     int* ptr = NULL;
     ptr = (int*)malloc(num*sizeof(int));
     if(NULL != ptr)//判断ptr指针是否为空
     {
         int i = 0;
         for(i=0; i<num; i++){
             *(ptr+i) = 0;
         }
     }
     free(ptr);//释放ptr所指向的动态内存
     ptr = NULL;
     return 0;
}

3. callocrealloc

3.1 calloc

C语⾔还提供了⼀个函数叫 calloc calloc 函数也⽤来动态内存分配。原型如下:
void* calloc (size_t num, size_t size);
函数的功能是为 num 个⼤⼩为 size 的元素开辟⼀块空间,并且把空间的每个字节初始化为0。
与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全
0
举个例⼦:
#include <stdio.h>
#include <stdlib.h>
int main()
{
     int *p = (int*)calloc(10, sizeof(int));
     if(NULL != p)
     {
         int i = 0;
         for(i=0; i<10; i++){
             printf("%d ", *(p+i));
         }
     }
     free(p);
     p = NULL;
     return 0;
}

输出结果:

所以如果我们对申请的内存空间的内容要求初始化,那么可以很⽅便的使⽤calloc函数来完成任务。

3.2 realloc

realloc函数的出现让动态内存管理更加灵活。
有时会我们发现过去申请的空间太⼩了,有时候我们⼜会觉得申请的空间过⼤了,那为了合理的时
候内存,我们⼀定会对内存的⼤⼩做灵活的调整。那 realloc 函数就可以做到对动态开辟内存⼤⼩的调整。

函数原型如下:

 

void* realloc (void* ptr, size_t size);
ptr 是要调整的内存地址
size 调整之后新⼤⼩
返回值为调整之后的内存起始位置。
这个函数调整原内存空间⼤⼩的基础上,还会将原来内存中的数据移动到 新 的空间。
realloc在调整内存空间的是存在两种情况:
        情况1:原有空间之后有⾜够⼤的空间
        情况2:原有空间之后没有⾜够⼤的空间

 

情况1:
当是情况1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发⽣变化。
情况2:
当是情况2 的时候,原有空间之后没有⾜够多的空间时,扩展的⽅法是:在堆空间上另找⼀个合适⼤⼩ 的连续空间来使⽤。这样函数返回的是⼀个新的内存地址。
由于上述的两种情况,realloc函数的使⽤就要注意⼀些。
#include <stdio.h>
#include <stdlib.h>
int main()
{
     int *ptr = (int*)malloc(100);
     if(ptr != NULL)
     {
         //业务处理
     }
     else
     {
         return 1; 
     }
     //扩展容量
 
     //代码1 - 直接将realloc的返回值放到ptr中
     ptr = (int*)realloc(ptr, 1000);//这样可以吗?(如果申请失败会如何?)
 
     //代码2 - 先将realloc函数的返回值放在p中,不为NULL,在放ptr中
     int*p = NULL;
     p = realloc(ptr, 1000);
     if(p != NULL)//判断
     {
         ptr = p;
     }
     //业务处理
     free(ptr);
     return 0;
}

4. 常⻅的动态内存的错误

4.1 对NULL指针的解引⽤操作

void test()
 {
     int *p = (int *)malloc(INT_MAX/4);
     *p = 20;//如果p的值是NULL,就会有问题
     free(p);
 }

所以使用动态内存函数是一定要养成习惯判断是否为NULL

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

void test()
 {
     int i = 0;
     int *p = (int *)malloc(10*sizeof(int));
     if(NULL == p)
     {
         exit(EXIT_FAILURE);//报错
     }
     for(i=0; i<=10; i++)//问题就出在i==10
     {
         *(p+i) = i;//当i是10的时候越界访问
     }
     free(p);
 }

4.3 对⾮动态开辟内存使⽤free释放

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

4.4 使⽤free释放⼀块动态开辟内存的⼀部分

void test()
 {
     int *p = (int *)malloc(100);
     p++;
     free(p);//p不再指向动态内存的起始位置
 }

4.5 对同⼀块动态内存多次释放

void test()
 {
     int *p = (int *)malloc(100);
     free(p);
     free(p);//重复释放
 }

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

void test()
 {
     int *p = (int *)malloc(100);
     if(NULL != p)
     {
         *p = 20;
     }
 }
int main()
 {
     test();
     while(1);
 }
忘记释放不再使⽤的动态开辟的空间会造成内存泄漏。
切记:动态开辟的空间⼀定要释放,并且正确释放

5. 动态内存经典笔试题分析

5.1 题⽬1:

void GetMemory(char *p)
 {
     p = (char *)malloc(100);
 }
void Test(void)
 {
     char *str = NULL;
     GetMemory(str);
     strcpy(str, "hello world");
     printf(str);
 }

输出结果是什么?

为什么?

这是因为str 传过去char*p是相当于值传递,是临时拷贝的str,函数销毁后,str还是不变,str还是NULL,所以没有输出。

5.2 题⽬2:

char *GetMemory(void)
 {
     char p[] = "hello world";
     return p;
 }
void Test(void)
 {
     char *str = NULL;
     str = GetMemory();
     printf(str);
 }

输出结果:

因为:

在这里p的地址的确传了回去的是地址后面的空间销毁了,所以才会出现这结果。

5.3 题⽬3:

void GetMemory(char **p, int num)
 {
     *p = (char *)malloc(num);
 }
void Test(void)
 {
     char *str = NULL;
     GetMemory(&str, 100);
     strcpy(str, "hello");
     printf(str);

//   free(str);
//   str = NULL;
 }

忘记了释放

5.4 题⽬4:

void Test(void)
 {
     char *str = (char *) malloc(100);
     strcpy(str, "hello");
     free(str);
     if(str != NULL)
     {
         strcpy(str, "world");
         printf(str);
     }
 }

忘记str = NULL,且释放空间后又把world放进去,//非法访问

6. 柔性数组

也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。
C99 中,结构中的最后⼀个元素允许是未知⼤⼩的数组,这就叫做『柔性数组』成员。
例如:
typedef struct st_type
{
     int i;
     int a[0];//柔性数组成员
}type_a;
有些编译器会报错⽆法编译可以改成:
typedef struct st_type
{
     int i;
     int a[];//柔性数组成员
}type_a;

6.1 柔性数组的特点:  

结构中的柔性数组成员前⾯必须⾄少⼀个其他成员
sizeof 返回的这种结构⼤⼩不包括柔性数组的内存
包含柔性数组成员的结构⽤malloc ()函数进⾏内存的动态分配,并且分配的内存应该⼤于结构的⼤⼩,以适应柔性数组的预期⼤⼩。

例如: 

typedef struct st_type
{
     int i;
     int a[0];//柔性数组成员
}type_a;
int main()
{
     printf("%d\n", sizeof(type_a));//输出的是4
     return 0;
}

6.2 柔性数组的使⽤

代码1:

#include <stdio.h>
#include <stdlib.h>
struct St
{
	char c;
	int n;
	int arr[0];
};
int main()
{
	struct St* ps = (struct St*)malloc(sizeof(struct St) + 10 * sizeof(int));//柔性数组开辟的空间:10 * sizeof(int)
	if (ps == NULL)
	{
		perror("malloc");
		return 1;
	}
	ps->c = 'w';
	ps->n = 100;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		ps->arr[i] = i;
	}
	//数组空间不够
	struct St* ptr = realloc(ps, sizeof(struct St) + 15 * sizeof(int));//改变柔性数组空间
	if (ptr != NULL)
	{
		ps = ptr;
	}
	else
	{
		perror("realloc");//报错
		return 1;
	}
	//...继续使用

	for (i = 10; i < 15; i++)
	{
		ps->arr[i] = i;
	}

	for (i = 0; i < 15; i++)
	{
		printf("%d ", ps->arr[i]);
	}
	printf("\n%d\n", ps->n);
	printf("%c\n", ps->c);

	//释放
	free(ps);
	ps = NULL;

	return 0;
}

还有另一种方式:

代码2:

struct St
{
	char c;
	int n;
	int* arr;//使用指针的方式访问
};


int main()
{
	struct St* ps = (struct St*)malloc(sizeof(struct St));
	if (ps == NULL)
	{
		perror("malloc");//报错
		return 1;
	}
	ps->c = 'w';
	ps->n = 100;

	ps->arr = (int*)malloc(10 * sizeof(int));//给柔性数组开辟空间
	if (ps->arr == NULL)//判断
	{
		perror("malloc-2");//报错
		return 1;
	}
	//使用
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		ps->arr[i] = i;
	}

	//数组空间不够
	int* ptr = (int*)realloc(ps->arr, 15 * sizeof(int));//这个可以,重点
	if (ptr == NULL)
	{
		perror("realloc");
		return 1;
	}
	else
	{
		ps->arr = ptr;
	}
	//使用
	for (i = 10; i < 15; i++)
	{
		ps->arr[i] = i;
	}
	for (i = 0; i < 15; i++)
	{
		printf("%d ", ps->arr[i]);
	}
	printf("\n%d\n", ps->n);
	printf("%c\n", ps->c);

	//释放两次
	free(ps->arr);
	ps->arr = NULL;

	free(ps);
	ps = NULL;

	return 0;
}

上述的 type_a 结构也可以设计为下⾯的结构,也能完成同样的效果:

//代码2
#include <stdio.h>
#include <stdlib.h>
typedef struct st_type
{
     int i;
     int *p_a;
}type_a;
int main()
{
     type_a *p = (type_a *)malloc(sizeof(type_a));
     p->i = 100;
     p->p_a = (int *)malloc(p->i*sizeof(int));
 
     //业务处理
     for(i=0; i<100; i++)
     {
         p->p_a[i] = i;
     }
 
     //释放空间
     free(p->p_a);
     p->p_a = NULL;
     free(p);
     p = NULL;
     return 0;
}

这样就简化很多了。

6.3 柔性数组的优势

上述 代码1 和 代码2 可以完成同样的功能,但是 ⽅法1 的实现有两个好处:

第⼀个好处是:⽅便内存释放
如果我们的代码是在⼀个给别⼈⽤的函数中,你在⾥⾯做了⼆次内存分配,并把整个结构体返回给⽤ ⼾。⽤⼾调⽤free可以释放结构体,但是⽤⼾并不知道这个结构体内的成员也需要free,所以你不能指望⽤⼾来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存⼀次性分配好了,并返回给⽤⼾⼀个结构体指针,⽤⼾做⼀次free就可以把所有的内存也给释放掉。
第⼆个好处是:这样有利于访问速度.
连续的内存有益于提⾼访问速度,也有益于减少内存碎⽚。(其实,我个⼈觉得也没多⾼了,反正你 跑不了要⽤做偏移量的加法来寻址)。
扩展阅读:
C语⾔结构体⾥的数组和指针。
好了今天就到这里了,都看到这里了点一个赞吧,感谢观看。

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

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

相关文章

【思考】只有实对称矩阵才能正交对角化吗?【矩阵的合同】

1&#xff1a;命题改写&#xff08;A可以正交对角化&#xff09; 2&#xff1a;左乘Q右乘Q逆&#xff08;Q转置&#xff09; 3&#xff1a;取转置 4&#xff1a;得证 总结 可以看到&#xff0c;矩阵如果可以正交对角化&#xff0c;那么一定是实对称矩阵。 另外&#xff0c;这…

shopee选品软件:如何利用Shopee选品软件优化你的销售业绩

在当今的电商市场中&#xff0c;选择适销的产品是成功的关键之一。然而&#xff0c;对于卖家来说&#xff0c;如何找到有潜力的产品并优化销售策略是一个具有挑战性的任务。幸运的是&#xff0c;有许多Shopee选品软件可以帮助卖家在Shopee平台上进行产品分析和选择。本文将介绍…

WPF 基于TableControl的页面切换

文章目录 前言其它项目的UserControl切换TableControl添加按钮&#xff0c;隐去TableItem的Header 结论 前言 我想用WPF简单实现一个按钮视图切换的效果&#xff0c;但是我发现别人的实现效果非常的麻烦。 其它项目的UserControl切换 我网上找了个开源的项目&#xff0c;他是…

100ask imx6ull 驱动(设备树)装载步骤

1.修改设备树文件 路径&#xff1a; vim 100ask_imx6ull-sdk/Linux-4.9.88/arch/arm/boot/dts/100ask_imx6ull-14x14.dtb 2. 回到linux内核目录下&#xff0c;执行命令 /*路径&#xff1a;100ask_imx6ull-sdk/Linux-4.9.88*/ make dtbs 将生成的arch/arm/boot/dts/100ask_…

【Spring】@SpringBootApplication注解解析

前言&#xff1a; 当我们第一次创建一个springboot工程时&#xff0c;我们会对启动类&#xff08;xxxApplication&#xff09;有许多困惑&#xff0c;为什么只要运行启动类我们在项目中自定义的bean无需配置类配置&#xff0c;扫描就能自动注入到IOC容器中&#xff1f;为什么我…

解决el-table组件中,分页后数据的勾选、回显问题?

问题描述&#xff1a; 1、记录一个弹窗点击确定按钮后&#xff0c;table列表所有勾选的数据信息2、再次打开弹窗&#xff0c;回显勾选所有保存的数据信息3、遇到的bug&#xff1a;切换分页&#xff0c;其他页面勾选的数据丢失&#xff1b;点击确认只保存当前页的数据&#xff1…

迅为RK3588开发板瑞芯微国产化工业ARM核心板AI人工智能

性能强 iTOP-3588开发板采用瑞芯微RK3588处理器&#xff0c;是全新一代AloT高端应用芯片&#xff0c;采用8nm LP制程&#xff0c;搭载八核64位CPU&#xff0c;四核Cortex-A76和四核Cortex-A55架构&#xff0c;主频高达2.4GHz&#xff0c;8GB内存&#xff0c;32GB EMMC。 四核心…

ACL和NAT

文章目录 ACL和NAT一、ACL概述及产生背景1、ACL访问控制列表2、ACL工作原理3、ACL种类4、ACL命令配置步骤4.1 ACL命令配置4.1 ACL配置步骤 二、NAT&#xff08;网络地址转换&#xff09;1、NAT概述2、NAT类型2.1 静态NAT与动态NAT 3、NATPT&#xff08;端口映射&#xff09;4、…

成都工业学院Web技术基础(WEB)实验二:HTML5表格、表单标签的使用

写在前面 1、基于2022级计算机大类实验指导书 2、代码仅提供参考&#xff0c;前端变化比较大&#xff0c;按照要求&#xff0c;只能做到像&#xff0c;不能做到一模一样 3、图片和文字仅为示例&#xff0c;需要自行替换 4、如果代码不满足你的要求&#xff0c;请寻求其他的…

Arduino使用定时器设置周期时间运行程序

1、用Arduino millis() 函数 实现一定程度上的多任务系统&#xff0c;可以设置不同时间的任务周期去执行对应的程序。比如需要10毫秒执行一次的程序、100毫秒执行一次的程序、1秒执行一次的程序。 2、Delay(ms)是延时函数&#xff0c;使用该延时函数&#xff0c;后面的程序将会…

租一台服务器多少钱决定服务器的价格因素有哪些

租一台服务器多少钱决定服务器的价格因素有哪些 大家好我是艾西&#xff0c;服务器这个名词对于不从业网络行业的人们看说肯定还是比较陌生的。在21世纪这个时代发展迅速的年代服务器在现实生活中是不可缺少的一环&#xff0c;平时大家上网浏览自己想要查询的信息等都是需要服…

统信UOS上图形化配置系统和应用代理

原文链接&#xff1a;统信UOS上图形化配置系统和应用代理 hello&#xff0c;大家好啊&#xff0c;今天我要给大家介绍的是在统信UOS操作系统上如何通过图形化界面配置系统代理和应用代理。在许多公司的内网环境中&#xff0c;直接访问互联网可能受到限制&#xff0c;但通常会提…

关于linux 磁盘占用排查问题

1.关于磁盘 查看整体磁盘占用大小 df -h 2. 先排除mysql 数据大小 查询库的大小 SELECT table_schema AS "Database", ROUND(SUM(data_length index_length) / 1024 / 1024, 2) AS "Size (MB)" FROM information_schema.TABLES GROUP BY table_schema…

ACL与NAT

目录 一、ACL &#xff08;一&#xff09;ACL基本理论 &#xff08;二&#xff09;ACL的类型 1.基本ACL 2.高级ACL 3.二层ACL &#xff08;三&#xff09;基本原理 &#xff08;四&#xff09;项目实验 通配符掩码 二、NAT &#xff08;一&#xff09;基本理论 &am…

【XR806开发板试用】+Linux小白上手开发笔记(2)——阿里云云方案

##0、前言 在之前文章中提到&#xff0c;在windows中搭建unbuntu对于新手小白来说非常不友好。因此一直在找解决方案&#xff0c;找到一条非常有意思的方案。希望对大家有点帮助。 1、环境搭建 方案核心————阿里云云 具体步骤如下&#xff1a; step1&#xff1a;注册。由…

【Python可视化系列】一文教会你绘制美观的柱状图(理论+源码)

一、前言 前面我详细介绍了如何绘制漂亮的折线图&#xff1a; 【Python可视化系列】一文彻底教会你绘制美观的折线图&#xff08;理论源码&#xff09; 本篇文章将教你绘制美观的柱状图。柱状图&#xff08;Bar Chart&#xff09;是一种常用的统计图表&#xff0c;用于展示不同…

Nginx 代理 MySQL 连接,并限制可访问IP

1.前言 我们的生产环境基本上都部署在云服务器上&#xff0c;例如应用服务器、MySQL服务器等。如果MySQL服务器直接暴露在公网&#xff0c;就会存在很大的风险&#xff0c;为了保证数据安全&#xff0c;MySQL服务器的端口是不对外开放的。 好巧不巧&#xff0c;线上业务遇到b…

自动化测试(一)配置selenium环境(带图文,防止踩坑)

目录 配置selenium环境 1. 安装setuptools 2. 安装selenium 3. 安装驱动 如何查看谷歌浏览器版本 上一章讲述了如何安装python环境&#xff0c;那么&#xff0c;这一章讲述的是&#xff0c;如何配置自动化测试&#xff08;selenium&#xff09;环境~吧&#x1f937;‍♀️…

设计模式详解---抽象工厂模式

继续前言&#xff0c;工厂模式中抽象工厂模式的讲解&#xff1a; 1. 前面的工厂模式有啥问题&#xff1f; 前面的工厂模式有这么个问题&#xff1a;一个产品就给了一个工厂&#xff0c;这样子如果产品变多&#xff0c;系统就会很复杂&#xff1a; 2. 解决方法 我们可以按照手…