动态内存管理总结

简介: 动态内存管理总结

1.前言:

对于很多参加竞赛或者平时在线OJ的同学来说,当大家使用C语言来书写代码的时候经常会遇到这样的情况:

题目要求数组的长度可变,并且要求输入一个整型当做数组的长度,我第一次遇到这个要求的时候是这样处理的:

int a=0;

scanf(“%d”,&a);

int arr1[a]={0};

正当我信心满满的提交答案时,发现编译器这样写时不通过的,但是从语法上来看没错啊?而且C99之后也支持C语言的变长数组的概念,但我们毕竟不是OJ的创始人,过不去的时候不能硬着头皮非要一条路走到黑,所以我们要另辟蹊径,这个时候,我们就需要动态内存管理来创建一个控制长度的数组了,所以,接下来我将说一说何为动态内存管理。

2.关于动态内存的概念理解:

首先我们先来理解一张图:

当我们写下一个程序的时候,其本质实际上是直接划分内存里面的空间,将空间分成有规律的几块,其具体要求如下:

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

我们平时开辟的整型,浮点型,结构体型甚至是数组开辟好之后都是在栈区直接开辟的,但我们今天要说的动态内存是在堆区开辟的,堆区正如上面所说,是专门给程序员开辟和释放的灵活可变的空间。

3.动态内存使用的基本方式和函数介绍:

1.基本函数:malloc calloc realloc free

就像文件操作一样,动态内存也强调其使用的步骤,其最关键的在于使用了动态内存后一定要及时释放,即使用free函数。

1.free函数:

其基本格式如下:free函数是用来释放掉我们所开辟的内存的

void free (void ptr);*

注意!!:free只是释放内存,但它不会将ptr指针自动放置成空指针,在指针章节我们说过,不及时置空指针,变成野指针是很麻烦的,所以我们使用完free后一定要自己手动置空指针,常见的方式为:

free(str1);
str1=NULL;

2.malloc函数:(引用头文件#include<stdlib.h>)

malloc函数即最基础的动态开辟的函数,它的作用是在堆区申请一块空间并让程序员使用,基本格式如下:

void malloc (size_t size);*

它的参数是要申请的堆区空间的大小,返回值是申请的这块动态内存的指针,我们注意到为void*类型的,对于这种类型的接收,我们只需要按我们所需的强转其指针即可。如同文件操作一样,使用动态开辟的函数使用后同样也要检验,其基本格式如下为例:

int*arr1=(int*)malloc(10*sizeof(int));
if(arr1==NULL)
{
    perror(:malloc failed");
    exit(-1);/return 1;
}

这便是我动态开辟的一个10个元素的数组,对于开辟失败的返回值,使用eixt(-1)和return 1都可以,其目的主要是跟return 0区分开,让程序员知道程序到底是因为什么结束的。后面的calloc realloc都遵循这个检验的步骤。

3.calloc函数:(#include<stdlib.h>)

calloc函数的用法与malloc大致相同,唯一不同的是它不仅开辟空间,还会将开辟的空间的每一个元素设置为0,基本格式如下:

void calloc (size_t num, size_t size);*

calloc的第一个参数为要开辟空间的元素个数,第二个为每一个元素的大小,所以calloc在开辟动态数组方面更加方便,但似乎不适合用来开辟结构体,它的返回情况和malloc一样。检验步骤同理。

4.realloc函数:(#include<stdlib.h>)

realloc的作用为调整由malloc和calloc开辟的空间的大小,使得其在调整动态内存的大小方面可以起到自动调整的作用,这对于使用顺序表以至于构建通讯录都是很方便的,格式如下:

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

它的第一个参数为用malloc realloc开辟的动态内存的地址,第二个参数为要调整的动态内存的大小**(注意这里不是累加的调整,是设定为多大就调整为多大)**,它的返回值为开辟的这块空间的地址,注意,我没有说返回值一定是原来开辟的地址,而是调整后的地址,这是由于堆区的开辟本身为碎片化的而不是有顺序的进行,realloc的调整倘若不影响其他动态空间,它返回的是原地址,但假如开辟的空间过大与其他冲突了,realloc就会重新找到一块地方开辟,同时将原来的那一块空间释放掉,这个时候它传过来的指针就是新地址的指针了。所以我在这里强调,一定不要把这个返回值单纯的看作一个源地址的指针,你就理解为这个空间的地址指针,你用之前的动态开辟的指针接收也就可以,用新的指针接收也可以。

realloc的特殊用法:当realloc对应的指针参数没有开辟动态空间的时候,此时使用realloc,它的用法跟malloc一样,直接为这个指针开辟一个对应的空间,并且返回这块的地址。

如下:

int*arr1=(int*)realloc(NULL,10*sizeof(int));//此时,realloc的作用与malloc作用相同
if(arr1==NULL)
{
    perror("realloc failed");
    exit(-1);/return 1;
}

那么,我们动态内存开辟的基本格式是什么呢?

void*arr1=(void*)malloc()/ realloc()/ calloc();//开辟内存
检验过程
{
}
使用过程
{
}
free(arr1);//内存释放过程
arr1=NULL;

使用动态内存开辟的时候,这几个步骤至关重要不能省略。

4.动态内存使用的常见错误总结:

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

例如:

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

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);
 }

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

例如:

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

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

例如:

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

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

例如:

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

6.动态开辟内存忘记释放(内存泄漏):忘记释放不再使⽤的动态开辟的空间会造成内存泄漏。

切记:动态开辟的空间⼀定要释放,并且正确释放。!!!!!!!且要手动置空

例如:

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

5.柔性数组:

回到前言中的问题,我们现在已经知道,利用动态开辟是可以操控数组长度的,但是C99中提到了这样一个概念,柔性数组,即在一个结构体内部包含多个成员变量,但最后一个成员是一个大小未知的数组,这就是柔性数组。

例如:

c
typedef struct st_type
{
 int i;
 int a[];//柔性数组成员
}type_a;
有时候在a的中括号里面写0也是可以的,但我本人建议按照定义来不写大小比较好理解。

1.柔性数组的特点:

• 结构中的柔性数组成员前⾯必须⾄少⼀个其他成员。

**• sizeof 返回的这种结构⼤⼩不包括柔性数组的内存。**你可以理解为它实际上是附加在结构体内部的一种特殊的结构,虽然在结构体里但开辟的空间是独立的

• 包含柔性数组成员的结构⽤malloc ()函数进⾏内存的动态分配,并且分配的内存应该⼤于结构的⼤

⼩,以适应柔性数组的预期⼤⼩。

2.柔性数组的使用:

柔性数组同样也是利用动态内存函数来开辟的,但特殊的是,它的大小是要独立于动态内存开辟的结构体而开辟的,具体如下:

type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));

如上图,我们独立于结构体之外单独为柔性数组开辟100个元素的空间,而不是算在一起开辟,这样开辟有利于我们理清结构体和柔性数组的大小。

倘若不用柔性数组,正常开辟也可以,如下:

type_a *p = (type_a *)malloc(sizeof(type_a));
 p->i = 100;
 p->p_a = (int *)malloc(p->i*sizeof(int));

所以我们说说柔性数组的好处:

1.第⼀个好处是:⽅便内存释放

如果我们的代码是在⼀个给别⼈⽤的函数中,你在⾥⾯做了⼆次内存分配,并把整个结构体返回给⽤

⼾。⽤⼾调⽤free可以释放结构体,但是⽤⼾并不知道这个结构体内的成员也需要free,所以你不能

指望⽤⼾来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存⼀次性分配好了,并返

回给⽤⼾⼀个结构体指针,⽤⼾做⼀次free就可以把所有的内存也给释放掉。通俗来讲就是,简单清晰,干净利落。

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

连续的内存有益于提⾼访问速度,也有益于减少内存碎⽚。(其实,我个⼈觉得也没多⾼了,反正你

跑不了要⽤做偏移量的加法来寻址)。注意,堆区大量使用动态开辟并不是一件好事,这样会导致堆区的空间碎片化,大量空间无法使用。

6.总结:

以上就是动态内存开辟的全部内容的,C语言之所以不适合竞赛,就在于它不能像C++那样使用vector调整长度,只能动态开辟数组来使用,但是动态开辟依旧是一个十分好用的操作,对于数据结构的链表和顺序表的节点创建和大小调整很重要,所以还是建议大家能熟练掌握动态内存开辟的内容。

目录
相关文章
|
Kubernetes 容器 Perl
使用kube-proxy让外部网络访问K8S service的ClusterIP
配置方式 kubernetes版本大于或者等于1.2时,外部网络(即非K8S集群内的网络)访问cluster IP的办法是: 修改master的/etc/kubernetes/proxy,把KUBE_PROXY_ARGS=”“改为KUBE_PROXY_ARGS=”–proxy-mode=userspace” 重启kube-proxy服务 在核心路由设备或者源主机上添加一条路由,访问cluster IP段的路由指向到master上。
4701 0
|
存储 SQL 分布式计算
数据湖 VS 数据仓库之争?阿里提出大数据架构新概念:湖仓一体
随着近几年数据湖概念的兴起,业界对于数据仓库和数据湖的对比甚至争论就一直不断。有人说数据湖是下一代大数据平台,各大云厂商也在纷纷的提出自己的数据湖解决方案,一些云数仓产品也增加了和数据湖联动的特性。但是数据仓库和数据湖的区别到底是什么,是技术路线之争?是数据管理方式之争?二者是水火不容还是其实可以和谐共存,甚至互为补充?本文作者来自阿里巴巴计算平台部门,深度参与阿里巴巴大数据/数据中台领域建设,将从历史的角度对数据湖和数据仓库的来龙去脉进行深入剖析,来阐述两者融合演进的新方向——湖仓一体,并就基于阿里云MaxCompute/EMR DataLake的湖仓一体方案做一介绍。
28596 2
数据湖 VS 数据仓库之争?阿里提出大数据架构新概念:湖仓一体
|
存储 缓存 Java
SpringBootWeb请求响应之前言及状态码的详细解析
SpringBootWeb请求响应 前言 在上一次的课程中,我们开发了springbootweb的入门程序。 基于SpringBoot的方式开发一个web应用,浏览器发起请求 /hello 后 ,给浏览器返回字符串 “Hello World ~”。
227 4
|
Java API 数据库
Spring Boot(16)——使用DataSource
使用DataSource 需要使用DataSource可以在pom.xml中添加spring-boot-starter-jdbc依赖,这会自动加入Spring Jdbc的依赖。还需要加入相应的JDBC驱动包的依赖,笔者这里使用的是MySQL的驱动。
9197 0
|
5月前
|
人工智能 自然语言处理 IDE
通义灵码:AI赋能编程,开启智能开发新时代
通义灵码是阿里云推出的一款专为开发者设计的智能编程助手,基于自主研发的大模型打造。它不仅具备代码生成、智能补全、代码优化和实时调试等功能,还通过垂直领域深度训练、多语言全栈支持以及与主流IDE无缝集成,大幅提升开发效率。真实案例显示,通义灵码可显著减少编码时间和错误率,助力开发者专注于业务逻辑。未来,它还将进一步理解业务需求、参与代码评审和跨团队协作,重新定义软件开发范式。立即体验,让AI赋能每一行代码!
389 8
|
10月前
|
存储 人工智能 关系型数据库
拥抱Data+AI|解码Data+AI助力游戏日志智能分析
「拥抱Data+AI」系列第2篇:阿里云DMS+AnalyticDB助力游戏日志数据分析与预测
拥抱Data+AI|解码Data+AI助力游戏日志智能分析
|
8月前
|
存储 数据采集 数据挖掘
Pandas数据应用:用户行为分析
本文介绍了如何使用Pandas进行用户行为分析,涵盖从基础概念到实际应用的多个方面。首先简要介绍了Pandas的安装与基本功能,接着详细讲解了数据加载、初步探索及常见问题(如数据缺失、重复记录和时间戳格式不统一)的处理方法。随后探讨了用户活跃度和路径分析等模式挖掘技巧,并总结了常见报错及避免措施。通过掌握这些内容,读者可以更高效地进行用户行为分析,提升产品设计和用户体验。
372 8
|
12月前
|
运维 监控 安全
什么是API?
API全称Application Programming Interface,即应用程序编程接口,是一些预先定义的函数,或指软件系统不同组成部分衔接的约定,用于传输数据和指令,使应用程序之间可以集成和共享数据资源。
1161 10
|
9月前
|
前端开发 搜索推荐 安全
你用过最好的CMS网站管理系统?
本文主要介绍了内容管理系统(CMS)的概念、功能和使用场景。CMS是一种位于WEB前端和后端管理前端内容的软件系统,用于发布、修改、审批、发布内容。文章提到,内容管理系统在满足个性化需求和提高工作效率方面有显著优势。
1060 1
|
10月前
|
机器学习/深度学习 人工智能 监控
人工智能在医疗健康领域的创新应用
本文旨在探讨人工智能技术在医疗健康领域的创新应用。通过分析AI如何助力疾病诊断、治疗计划制定、患者监护以及药物研发,本文揭示了AI技术为现代医疗服务带来的革命性变化。此外,文章还讨论了实施这些技术时面临的挑战和未来发展趋势,为医疗行业的数字化转型提供了深入见解。