当前位置:网站首页>C动态内存管理详解
C动态内存管理详解
2022-07-24 03:09:00 【浪雨123】
目录
为什么存在动态内存分配
一般我们在存放程序运行需要的数据时,会选择数组来进行存放,但数组的大小是不可变化的,设定了多少就是多少。程序的运行是动态的,我们往往并不知道产生的数据要有多大的空间来存放,如果用数组给的空间太大,会造成内存空间的极大浪费,运行效率也有所降低,给的太少又不够用,导致程序崩溃。因此我们需要一种动态的内存分配机制,如果检测到内存不够用,会自动开辟新的内存空间,用来存放数据。
动态内存分配函数
在C语言中,内存分配函数有3个,分别是 malloc() , calloc(), realloc()
在使用动态内存函数时切记,切记要引头文件 <stdlib.h>(无数血与泪的教训)
还有就是不再使用分配的内存后,要记得用free()来释放,否则会造成内存泄露的。
后面再解释内存泄漏,接下来介绍这3个内存开辟函数
malloc
void* malloc (size_t size)
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
如果开辟内存失败则返回空指针,因此在开辟空间时需要对返回的指针进行检查
//用malloc开辟10个int整型空间
int main()
{
int* p = (int*)malloc(sizeof(int) * 10);
if (NULL == p)
{
printf("fail\n");
return 1;
}
free(p);
p = NULL;
return 0;
}
//正常的开辟流程calloc
void* calloc (size_t num, size_t size);
calloc与malloc在功能上的差别就是calloc能够将开辟的空间都初始化为0,如果需要将开辟的空间初始化,可以选择calloc,另外在参数上也略有区别,calloc要传两个参数,一个参数是要开辟某类型空间的个数,另一个是要开辟的空间的类型。实际上就是把malloc的参数拆开来,上面的malloc( sizeof(int) *10 ) 变成calloc为 calloc( 10, sizeof(int) )
realloc
前面两个函数可能会让你产生一些疑惑,感觉和使用数组开辟的空间区别不大,并没有体现出动态开辟,那么realloc就能够解决这些问题,我们先看看realloc的声明
void* realloc (void* ptr, size_t size)
由声明可知,第一个参数是一个指针,第二个参数是一个无符号整型数字,分别是什么含义呢。第一个参数需要传指向我们之前开辟过的空间的指针,在之前的例子中,我们尝试用malloc开辟了10个整型空间,并用指针变量p来维护这块空间,假设这10个整型空间不够使用了,我还想再扩增10个整型空间,且保存之前产生的数据,这个时候realloc函数就起到了很大作用。我们把之前维护开辟空间的指针p传过去,然后第二个参数传重新开辟空间的大小,我们想扩增10个容量,加上之前的就是20个空间,然后realloc会返回一个指针,返回的指针分为三种情况1.空间开辟失败,返回一个空指针
2.原先空间的后面没有被使用的空间能够满足扩增的需求,那就返回维护原先空间的指针
3.原先空间的后面没有被使用的空间不满足扩增的需求,寻找一块新空间,返回新空间的起始地址
接下来我们详细分析一下
int main()
{
int* p = (int*)malloc(sizeof(int) * 10);
if (NULL == p)
{
printf("fail\n");
return 1;
}
// ......
......
//经过一段程序的使用,发现原先开辟的空间不够使用,要再扩增10个
int *ps = (int*)realloc( p, sizeof(int)*20 )
if( NULL == ps)
{
printf("fail\n");
return 1;
}
p = ps; //如果还想让之前的指针p来维护这块新空间,可以进行这一步操作
ps = NULL;
//.......
free(p);
p = NULL;
return 0;
}
造成内存泄漏的原因
很多程序一旦运行起来,除了设备停机,基本就一直处于运行状态,如果我们malloc了一块很大的空间,并且在使用完后没有用free()来释放,因为c/c++是没有自动回收机制的,这就导致我们malloc的这块空间在内存中一直处于被占用的状态,不用之后,程序不会再管维护这块空间的指针,就很可能导致维护空间的指针的丢失,从而失去了对这块空间的控制,在程序运行期间,再也找不到这块空间,也没法再使用这块空间,即造成内存泄漏。
而我们平时写的小程序,程序运行一会后就关闭了,此时程序所占用的全部空间归还操作系统,但我们要养成随用随释放的好习惯,不然以后接触大项目,内存泄漏很危险。
造成内存碎片化的原因
我们在用动态内存函数开辟空间时,使用的是内存的堆区,如果我们频繁的使用malloc
realloc来开辟内存空间,会把堆区分成一块一块的,每开辟的两块空间可能就夹杂着一些没用过的空间,而这些空间很细碎很难再被使用,会导致内存的利用率降低。
柔性数组
柔性数组的定义和特点
C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员
特点:1.结构中的柔性数组成员前面必须至少一个其他成员。
2.sizeof 返回的这种结构大小不包括柔性数组的内存。
3.包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应 该大于结构的大小,以适应柔性数组的预期大小
typedef struct stu
{
int age;
char name[];
}S;
//char name[]就是一个柔性数组,它的大小可以是未定义的
柔性数组的使用和优势
//柔性数组的使用
struct S
{
int n;
int arr[];
};
int main()
{
structu S s;
//这样创建会导致柔性数组没有空间
struct S* ps= (struct S*)malloc(sizeof(struct S) + 40);
//40是给柔性数组arr预留的大小
}
struct S
{
int n;
int *arr;
};
int main()
{
struct S* ps = (struct S*)malloc( sizeof(struct S) );
ps->arr = (int*)malloc(40);
}
//此为普通结构体的使用柔性数组相对于普通结构体的优势
1.相比较与柔性数组,上面普通结构体会多消耗内存,也要开辟和释放两次
2.因为普通结构体要malloc两次,会造成内存碎片,导致内存利用率降低,运行速度略降低
使用动态内存函数常见的错误
1.对NULL指针进行解引用
造成这种情况的原因主要是不对内存开辟函数返回的指针进行检查,如果不检查,而恰好开辟失败,就造成对空指针的引用
void test()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}2.对开辟的空间越界访问
这种情况就是对空间的使用不当,只开辟了10块空间,错误访问到第十一块空间
3.对非动态开辟的内存空间使用free()来释放空间
free()只能用来释放动态开辟的空间
void test()
{
int a =0;
int *p = &a;
free(p);
}
//这样是错误的4.对同一块动态内存多次释放
一块动态开辟内存释放一次就好,释放多次是错误的
5.使用free释放时只释放了其中一部分
很多时候我们动态开辟了一块空间,用指针p来维护,p指向这块空间的起始地址,但是在使用这块空间时,可能会导致p不再指向起始位置,在不把p返回到起始位置时就把p释放,这样是错误的。
void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}6.忘记释放已开辟的内存(内存泄漏)
可能很多同学说自己早已经记住了要释放已开辟的内存,不会造成内存泄露的,事实上我们的逻辑并没有我们认为的那么严谨,不妨看看下面一段程序
int test()
{
int* p = (int*)malloc(sizeof(int) * 2);
if (NULL == p)
{
printf("fail\n");
return -1;
}
scanf("%d %d", p, p + 1);
if ( *p > *(p+1) )
{
printf("较大的数是:");
return *p;
}
else
{
printf("较大的数是:");
return *(p+1);
}
free(p);
p = NULL;
}
int main()
{
int m = test();
printf("%d", m);
return 0;
}上面是实现返回较大值的简单程序,如果你感觉这段程序没有毛病的话,那么非常抱歉,你已经造成了内存泄漏,先分析一下原因,我们在test函数内开辟了两个int 类型的空间,并输入两个值进行对比,到目前为止都没有毛病,毛病就出在我们在进行对比后返回一个返回值,直接就离开了test()函数,在离开之前我们可没有对p进行free释放啊,而p又是在test内部定义的,其只能在test内部使用,离开test,p就还给了内存,变成了野指针,也就是说我们再也无法通过p来找到这块未释放的空间,在程序运行期间,这块空间就被泄露了
是不是感觉内存泄漏没有那么简单,如果你一眼就看出了问题,那么你的洞察和逻辑能力很强,很聪明,要继续保持这份警觉,如果你没有看出,那么现在你已经被种上了这份警觉
以后多留意观察,不可盲目自信。
边栏推荐
- summernote富文本编辑器
- How will you answer the "Hello world" challenge written in C language?
- String.split()最详细源码解读及注意事项
- About Aries framework addition, deletion, modification and query - query demo
- Binary tree traversal (day 74)
- Leetcode stack and queue questions
- 【AMC】联邦量化
- Hcip day 9 notes (OSPF routing feedback, routing policy, and Configuration Guide)
- SSM based blog system [with background management]
- Ways to improve the utilization of openeuler resources 01: Introduction
猜你喜欢

The first edition of Niuke brush question series (automorphic number, return the number of prime numbers less than N, and the first character only appears once)

The next stop of data visualization platform | gifts from domestic open source data visualization datart "super iron powder"

如何获取步态能量图gei

在openEuler社区开源的Embedded SIG,来聊聊它的多 OS 混合部署框架

SkyWalking分布式系统应用程序性能监控工具-上

SolidWorks CAM data cannot be recovered because a processed part has been detected.

Services et configurations FTP

O3DE 的Lumberyard 游戏引擎

String.split() the most detailed source code interpretation and precautions

198. House raiding
随机推荐
FTP服務與配置
Diversity of SIGIR '22 recommendation system papers
CMT 注册——Google Scholar Id,Semantic Scholar Id,和 DBLP Id
To forge ahead on a new journey, the city chain science and technology carnival was grandly held in Xiamen
JVM initial
AcWing 4499. 画圆 (相似三角形)
Lcd1602——斌哥51
TCP data transmission and performance
The function of SIP account - tell you what is SIP line
Attack and defense world web practice area (view_source, get_post, robots)
Lumberyard game engine of o3de
c语言小练习
Redux Usage Summary
Generate 13 bit barcode
322. Change
The next stop of data visualization platform | gifts from domestic open source data visualization datart "super iron powder"
Description of relevant resolutions in video on demand
New definition of mobile communication: R & scmx500 will improve the IP data throughput of 5g devices
How to get gait energy map Gei
JVM初始