【动态内存管理】C语言

news2024/12/23 0:18:28

前言:
为什么会存在动态内存分配

我们以前学过的开辟空间的方式有两个特点:
1 空间开辟大小是固定的;
2.数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配;
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了,这时候就只能试试动态存开辟了

目录

  • 1.动态内存函数的介绍
    • 1.1 malloc和free
    • 1.2 calloc
    • 1.3 realloc
  • 2. 常见的动态内存错误
    • 2.1 对NULL指针的解引用操作
    • 2.2 对动态开辟空间的越界访问
    • 2.3 对非动态开辟内存使用free释放
    • 2.4 使用free释放一块动态开辟内存的一部分
    • 2.5 对同一块动态内存多次释放
    • 2.6 动态开辟内存忘记释放(内存泄漏)
  • 3.C/C++程序的内存开辟
  • 4. 柔性数组
    • 4.1柔性数组的特点
    • 4.2 柔性数组的使用
    • 4.3 柔性数组的优势

1.动态内存函数的介绍

1.1 malloc和free

以上两个函数都在头文件:stdlib.h

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

void* malloc (size_t size);

功能:

这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
成功时,指向函数分配的内存块的指针。
此指针的类型始终为 ,可以强制转换为所需类型的数据指针,以便可取消引用。
如果函数无法分配请求的内存块,则返回空指针。void*

详情见以下:https://legacy.cplusplus.com/reference/cstdlib/malloc/?kw=malloc
C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的,函数原型如下:

void free (void* ptr);

功能:

free函数用来释放动态开辟的内存。
如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
如果参数 ptr 是NULL指针,则函数什么事都不做。

详情如下:https://legacy.cplusplus.com/reference/cstdlib/free/?kw=free

以下通过代码进行相应的了解:

#include<stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main()
{
	//int arr[10];//向内存申请了40个字节
	int* p = (int*)malloc(10*sizeof(int));
	int* ptr = p;
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//使用
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*ptr = i;
		ptr++;
	}
	//释放
	free(p);
	p = NULL;
    ptr = NULL;
	return 0;
}

1.2 calloc

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

void* calloc (size_t num, size_t size);

功能:

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

详情如下:https://legacy.cplusplus.com/reference/cstdlib/calloc/?kw=calloc
还是通过代码进行相应的了解:

int main()
{
	int* p = (int*)calloc(10, sizeof(int));
	if (p == NULL)
	{
		perror("calloc");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
	//释放
	free(p);
	p = NULL;
	return 0;
}

就相当于:

calloc = malloc+memset

注意:
如果我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成任务,如果不初始化就使用malloc函数来实现。

1.3 realloc

函数原型如下:

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

功能:

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

详情如下:https://legacy.cplusplus.com/reference/cstdlib/realloc/?kw=realloc
realloc在调整内存空间的是存在两种情况:

情况1:原有空间之后有足够大的空间
当为这种情况的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化在这里插入图片描述

情况2:原有空间之后没有足够大的空间
当为这种情况时 原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。在这里插入图片描述
通过代码进行演示:

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
		return 1;
	//使用
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
	//
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	//增加空间
	int* ptr = (int*)realloc(p, 80);
	//当realloc开辟失败的是,返回的是NULL
	if (ptr != NULL)
	{
		p = ptr;
		ptr = NULL;
	}
	//释放
	free(p);
	p = NULL;

	return 0;
}

2. 常见的动态内存错误

2.1 对NULL指针的解引用操作

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

2.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++)
 {
 *(p+i) = i;//当i是10的时候越界访问
 }
 free(p);
}

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

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

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

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

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

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

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

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

忘记释放不再使用的动态开辟的空间会造成内存泄漏,动态开辟的空间一定要释放,并且正确释放 。

3.C/C++程序的内存开辟

在这里插入图片描述

  1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结 束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是 分配的内存容量有限。
    栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返 回地址等。
  2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分 配方式类似于链表。
  3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
  4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。

有了这幅图,我们就可以更好的理解在《C语言初识》中讲的static关键字修饰局部变量的例子了:

实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。 但是被static修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,直到程序结束才销毁所以生命周期变长

4. 柔性数组

概念:

也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员

例如:

typedef struct st_type
{
 int i;
 int a[0];//柔性数组成员
}type_a;

4.1柔性数组的特点

结构中的柔性数组成员前面必须至少一个其他成员。
sizeof 返回的这种结构大小不包括柔性数组的内存。
例如:

> `typedef struct st_type
{
	int i;
	int a[0];//柔性数组成员
}type_a;

int main()
{
	printf("%d\n", sizeof(type_a));
	return 0;
}

输出结果为:
在这里插入图片描述

包含柔性数组成员的结构用malloc函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小

4.2 柔性数组的使用

 struct s
{
	int i;
	int a[0];//柔性数组成员
};

int main()
{
	int i = 0;
	struct s* p = (struct s*)malloc(sizeof(struct s) + 4 * sizeof(int));
	if (p == NULL)
	{
		return 1;
	}
	p->i = 4;
	for (i = 0; i < 4; i++)
	{
		scanf("%d", &(p->a[i]));
	}
	printf("%d\n", p->i);
	for (i = 0; i < 4; i++)
	{
		printf("%d ",p->a[i]);
	}

	//释放
	free(p);
	return 0;
}

这样柔性数组成员相当于获得了4个整型元素的连续空间。

4.3 柔性数组的优势

上述代码可以改写为如下代码:

struct S
{
	int n;
	int* arr;
};

int main()
{
	struct S*ps = (struct S*)malloc(sizeof(struct S));
	if (ps == NULL)
		return 1;

	ps->n = 100;
	
	int* ptr = (int*)malloc(4 * sizeof(int));
	if (ptr == NULL)
	{
		return 1;
	}
	else
	{
		ps->arr = ptr;
	}
	//使用
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		scanf("%d", &(ps->arr[i]));
	}
	
	//打印
	printf("%d\n", ps->n);
	for (i = 0; i < 4; i++)
	{
		printf("%d ", ps->arr[i]);
	}

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

	return 0;
}

上述代码可以完成同样的功能,但是第一种的实现有两个好处:
第一个好处是:方便内存释放

如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉

第二个好处是:这样有利于访问速度.

连续的内存有益于提高访问速度,也有益于减少内存碎片。(其实,我个人觉得也没多高了,反正你跑不了要用做偏移量的加法来寻址)

最后给大家推荐一个扩展阅读:
https://coolshell.cn/articles/11377.html
大家有兴趣可以去阅读一下。

总结:

到此,关于动态内存管理的知识就到这里了,下期我会整理一些有关动态内存的一些经典笔试题给大家巩固学习!

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

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

相关文章

Qt基于CTK Plugin Framework搭建插件框架--插件通信【注册接口调用】

文章目录一、前言二、插件完善2.1、添加接口文件2.2、添加接口实现类2.3、服务注册&#xff08;Activator注册服务&#xff09;三、接口调用四、接口 - 插件 - 服务的关系4.1、1对14.2、多对14.3、1对多一、前言 通过Qt基于CTK Plugin Framework搭建插件框架–创建插件一文&am…

解决方案|Keithley吉时利源表测试软件的典型应用及案例介绍

数字源表又称源测量单元(SMU)&#xff0c;是数字万用表(DMM)、电压源、实际电流源、电子负载和脉冲发生器的有用功能集成在仪器中&#xff0c;相当于电压源、电流源、电压表、电流表和电阻表的综合体可以作为四象限电压源或电流源提供精确的电压或电流&#xff0c;同时测量电流…

聚类模型(K-means聚类,系统聚类,DBSCAN算法)

所谓的聚类&#xff0c;就是将样本划分为由类似的对象组成的多个类的过程。聚类后&#xff0c;我们可以更加准确的在每个类中单独使用统计模型进行估计、分析或预测&#xff1b;也可以探究不同类之间的相关性和主要差异。聚类和分类的区别&#xff1a;分类是已知类别的&#xf…

Kafka 生产者

Kafka 生产者 生产者就是负责向 Kafka 发送消息的。 生产者业务逻辑 &#xff08;生产者业务逻辑流程&#xff09; 生产者开发示例 一个正常的生产逻辑流程如下&#xff1a; 配置生产者客户端参数及创建相应的生产者实例 构建待发送的消息 发送消息 关闭生产者实例 生…

CSS权威指南(八)基本元素框

文章目录1.基本元素框2.内边距3.边框4.轮廓5.外边距1.基本元素框 文档中每个元素都会生成一个矩形框&#xff0c;我们称之为元素框。这个框体描述元素在文档布局中所占的空间。因此&#xff0c;元素框之间是有影响的&#xff0c;涉及位置和尺寸。 &#xff08;1&#xff09;宽…

如何在 Excel VBA 中插入行

在本文中,我将解释如何使用VBA(Visual Basic for Applications)在Excel中插入行。VBA 是一种编程语言,适用于在Excel和其他Office程序中工作的人员,因此可以通过编写所谓的宏来自动化Excel中的任务。使用VBA编码,我们可以执行Excel中执行的所有大多数任务,就像复制、粘贴…

【手写 Vue2.x 源码】第十六篇 - 生成 render 函数 - 代码拼接

一&#xff0c;前言 上篇&#xff0c;生成 ast 语法树 - 构造树形结构部分 基于 html 特点&#xff0c;使用栈型数据结构记录父子关系开始标签&#xff0c;结束标签及文本的处理方式代码重构及ast 语法树构建过程分析 本篇&#xff0c;使用 ast 语法树生成 render 函数 - 代…

双软认证-深圳市

双软认证是软件企业的认证和软件产品的登记&#xff0c;企业申请双软认证除了获得软件企业和软件产品的认证资质&#xff0c;同时也是对企业知识产权的一种保护方式&#xff0c;更可以让企业享受国家提供给软件行业的税收优惠政策。 想要在这个残酷的市场中生存下去的话&#x…

cc1200 Sub-1 GHz RF Transceivers 开发

一些应用需要定制开发无线串口、指定发送频点、调制方式、加密传输等等&#xff0c;需要使用无线数据的传输场景&#xff0c;需要使用公用频段进行数据传输。一些场景需要使用Sub-1 GHz频点进行数据传输&#xff0c;比如无线串口&#xff0c;其他无线申请&#xff0c;在国内选择…

集群调度情况

1 集群调度 2 调度简介 Scheduler是kubernetes的调度器&#xff0c;主要任务是把定义的pod分配到集群的节点上。听起来非常简单&#xff0c;但有很多要考虑的问题 公平&#xff1a; 如何保证每个节点都能被分配资源 资源高效利用&#xff1a;集群所有资源最大化被使用 效率&…

【 uniapp - 黑马优购 | 购物车页面(1)】如何创建购物车编译模式、 商品列表区域实现

个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名大二在校生&#xff0c;讨厌编程&#x1f38b; &#x1f43b;‍❄️个人主页&#x1f947;&#xff1a;小新爱学习. &#x1f43c;个人WeChat&#xff1a;hmmwx53 &#x1f54a;️系列专栏&#xff1a;&#x1f5bc…

LeetCode[692]前K个高频单词

难度&#xff1a;中等题目&#xff1a;给定一个单词列表 words和一个整数 k&#xff0c;返回前 k个出现次数最多的单词。返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率&#xff0c; 按字典顺序 排序。示例 1&#xff1a;输入: words ["i"…

【异常】记一次因scripts编写错误导致无法正常build的问题

一、npm 与 scripts之间的关系 Node 开发离不开 npm&#xff0c;而脚本功能是 npm 最强大、最常用的功能之一。 npm 允许在package.json文件里面&#xff0c;使用scripts字段定义脚本命令。 比如以下&#xff1a; "scripts": {"dev": "vue-cli-se…

【C++】引用详解

作者&#xff1a;阿润菜菜 专栏&#xff1a;C &#x1f3c3;&#x1f3c3;&#x1f3c3;&#x1f3c3;&#x1f3c3;&#x1f3c3; 本文目录 概念及用法 特性 使用场景 1.做参数 2. 做返回值 从函数栈帧角度理解引用 传值、传引用效率比较 引用和指针的区别 概念及用法 引…

洛谷 P1194 买礼物 (图论 最小生成树)

鸽了好几天了今天写个洛谷的题解 题目描述 又到了一年一度的明明生日了&#xff0c;明明想要买 BB 样东西&#xff0c;巧的是&#xff0c;这 BB 样东西价格都是 AA 元。 但是&#xff0c;商店老板说最近有促销活动&#xff0c;也就是&#xff1a; 如果你买了第 II 样东西&#…

Python OpenCV 数字验证码 字母验证码 图片验证码 自动识别方案 第三方库 识别成功率较高 通用解决方案

前言 在学习的前期可使用现有封装好的轮子试试效果,实际调试能否满足需求。使用已经造好的轮子的好处就是能快速解决当下的问题。若能就继续使用,若不能就接入下一步的深度学习模型训练,其实再验证码识别业务场景大多是情况下用于自动化测试仅针对公司内某一单一的业务线,而…

既然有MySQL了,为什么还要有MongoDB?

目录一、基本概念走起二、MongoDB的主要特征三、MongoDB优缺点&#xff0c;扬长避短1、优点2、缺点四、何时选择MongoDB&#xff1f;为啥要用它&#xff1f;1、MongoDB事务2、多引擎支持各种强大的索引需求3、具体的应用场景4、以下是几个实际的应用案例&#xff1a;5、选择Mon…

gcc后续——链接时的静态库和动态库

本篇文章是链接阶段静动态库的理解&#xff0c;点击查看gcc四个阶段 文章目录1 . 库检测linux所用库查找库的位置2. 动静态库的感性理解1. 动态库的理解2. 静态库的理解3. 静动态库整体理解1. 静态库和静态链接2. 动态库和动态链接3. 静动态库对比1.查询当前linux所用库2. 查看…

【洛谷】P1966 [NOIP2013 提高组] 火柴排队

其实这题本身并不难&#xff0c;考的知识点就是归并排序和逆序对&#xff1b;那么难点在哪呢&#xff1f;就在如何发现这题是个逆序对&#xff1a;至少读到这里我们可以知道&#xff0c;虽然火柴高度是唯一的&#xff0c;但我们不可能直接开一个 max long int 大小的数组&#…

数据库分片

文章目录一、为什么要分片二、什么是数据分片1、垂直分片2、水平分片三、常用分片策略1、Range2、Hash四、相关中间件1、Sharding-Sphere2、Sharding-jdbc一、为什么要分片 从性能方面来说&#xff0c;由于关系型数据库大多采用B树类型的索引&#xff0c;在数据量超过阈(yu)值…