首页 > 系统相关 >C语言动态内存管理

C语言动态内存管理

时间:2022-11-30 16:00:59浏览次数:47  
标签:动态内存 管理 free C语言 内存 数组 柔性 NULL 指针

在学习和使用C语言的过程中经常要编写管理内存的程序,往往提心吊胆。若是不想踩雷,唯一的办法就是深入理解内存管理,发现所有的陷阱并排除他们。


内存的使用方式

内存主要有三种分配方式:

(1)在栈(Stack)上创建。可以在栈区创建数个局部变量或者局部数组。函数结束执行时这些内存被自动释放。

(2)从静态区(Static)分配。在静态区创建全局变量,static修饰的变量和常量字符串都在静态区存储。这些内存在程序的整个运行期间都存在。

(3)从堆区(Heap)分配。又称动态内存分配。使用动态内存可以非常灵活,但问题也最多。

C语言动态内存管理_柔性数组


动态内存


·使用库函数进行动态内存管理

在C语言中使用malloccallocreallocfree等库函数进行动态内存管理。


malloc

void* malloc(size_t size);

malloc用于开辟一块动态内存空间。参数为开辟的内存大小,单位是字节,返回所开辟内存空间的地址。

使用时需要注意以下几点:

  1. 使用malloc可能出现开辟内存失败的情况,此时返回值为NULL
  2. malloc的返回值是void*类型,用指针接收malloc开辟的空间时,注意进行类型转换
  3. 对于size为0的情况,C语言标准未规定,可能会造成危险,进行不要进行此操作
  4. 使用malloc开辟的空间中初始是随机值

calloc

void* calloc (size_t num,size_t size);

calloc用于开辟一块动态内存空间。参数为要开辟空间中元素的个数和元素大小,单位是字节,返回值为所开辟的内存空间的地址。其与malloc不同的地方除参数列表外,也会将所开辟的空间中的值自动初始化为0

使用时需要注意:

  1. 可能出现开辟内存失败情况,此时返回值为NULL
  2. 用指针接收所开辟的空间时,注意类型转换

realloc

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

realloc用于调整已开辟内存空间的大小。参数为已开辟内存的地址和新的空间大小,单位是字节。

realloc的内存调整

realloc的内存调整有两种情况

(一) 进行内存增加调整时,若原来内存后面的空间足够,则直接在原来内存空间的后面进行内存的追加,此时返回原来内存的指针

(二) 若原来内存后面的空间不足,则另外开辟一块新的空间,将原来空间内的数据拷贝到新空间中,释放原来的空间,返回新空间的地址

即realloc的返回值有多种可能。在实际使用过程中,也可能会发生调整内存失败的情况,此时realloc的返回值为NULL。若直接使用原来空间的指针(假设为p)进行接收,且此时开辟空间失败返回NULL,则原来空间的指针就会被赋值为空指针,造成原来空间的遗失。因此应该先使用另外一个指针变量(假设为ptr)接收realloc的返回值,接着对ptr进行判断,若ptr不为空指针,则可以放心得将这块调整后的空间交给p

C语言动态内存管理_柔性数组_02


free

void free( void *memblock );

free用于主动释放一块动态内存空间。参数为指向一块内存的指针,返回值为空。

注意:

(1)程序结束时,动态内存自动回收,但最好用free主动进行内存释放

(2)free只是将相应的内存进行释放,此时该内存的指针依旧指向被释放的内存,即造成了一个“野指针”。为了保证安全,在释放内存时应彻底将内存和指向该内存的指针切断联系,即将指针设置为空指针

(3)如果参数为NULL,则free不进行任何操作。

(4)free只针对动态开辟的空间


常见的动态内存错误及其对策


一.使用了未分配成功的内存

这个问题常见于开辟/调整动态内存失败但仍使用的情况。在使用一块内存之前,最好先检查指针是否为NULL,用​​if(p == NULL)​​​或​​if(p != NULL)​​进行防错处理。


二.操作越过了内存的边界

例如在操作数组时,经常发生数组下标多1或少1的情况。特别是在for循环中,循环次数的错误容易造成内存的越界


三.忘记释放内存,导致内存泄漏

含有这种错误的函数或语句,每被调用或执行一次都会吃掉一块内存空间。刚开始你可能不会发现,但终有一次会发生错误:内存不足

动态内存的开辟和释放必须成对存在,malloc/free必须成对调用,程序中每出现一次开辟就必须有一个释放与其匹配,否则一定会发生错误。

一段问题代码:

void GetMemory(char* p)//p是str的一份临时拷贝!!!
{
p = (char*)malloc(100);
}//结束时,p销毁,开辟的空间未被free,且无法找到,造成内存泄漏

void Test(void)
{
char* str = NULL;
GetMemory(str);//传值调用
strcpy(str, "hello world");//str还是NULL,崩溃
printf(str);
}

//1.程序崩溃
//2.内存泄漏

int main()
{
Test();
return 0;
}


四.多次释放内存

这种情况与上面的情况恰好相反。如果一个指针是空指针,那么你对它进行释放10次也不会出现问题(但没人会这样做),但若其不是空指针,第二次释放时就会出现问题。避免造成这种问题的方式有两个:1.尽量做到谁使用谁释放2.释放后立即将指针设置为NULL

一个使用动态内存的良好案例:

int main()
{
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
return 0;
}
else
{
//使用
}

free(p);
p=NULL;

return 0;
}


五.使用了已经被释放的内存

有三种情况:

(1)程序中的调用关系过于复杂,实在难以分清某个内存块是否被释放。此时应该对程序进行梳理,或者直接重新设计

(2)return了已经被销毁的内存。注意不要返回栈内存的指针,因为函数调用结束时栈内存会自动销毁

(3)释放内存后没有将指针置空,导致产生“野指针”。用free释放内存后,应立即将指针置为空指针。

一段问题代码:

char* GetMemory(void)
{
char p[] = "hello world";
return p;
}//虽然返回了字符数组的地址,但该地址的空间已被销毁

//char* GetMemory(void)
//{
//char* p = "hello,world";//p指向一个字符串常量,字符串常量存储在静态区,所以不会被销毁
//return p;
//}

void Test(void)
{
char* str = NULL;
str = GetMemory();
printf(str);
}

int main()
{
Test();
return 0;
}

另一段问题代码:

void Test(void)
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);
if (str != NULL)
{
strcpy(str, "world");//非法访问动态内存
printf(str);
}
}

柔性数组


是什么

C99标准中引入了柔性数组的概念。结构体中最后一个成员变量可以是一个未知大小的数组,该数组被称为柔性数组。平常说的长度为0的数组即是指的柔性数组。

struct S 
{
int n;
int arr[0];//这是一个柔性数组
}

注:计算上面结构体S的大小时不包括arr[]的大小

我们可以通过动态内存操作开辟和调整柔性数组的大小:

struct S* ps=(struct S*)malloc(sizeof(struct S)+sizeof(数组大小));
if(ps != NULL)
{
//使用
}

/...
.../

free(ps);
ps=NULL;

调整大小:

struct S* ptr = realloc(ps, 数组大小);//调整柔性数组大小
if(ptr != NULL)
{
ps=ptr;
}

/...
.../

free(ps);
ps=NULL;


柔性数组的特点

柔性数组具有以下特点:

(1)柔性数组成员前面必须有其他成员

(2)sizeof()返回具有柔性数组成员的结构体大小时不包括柔性数组的大小

(3)使用malloc对含有柔性数组成员的结构体进行动态内存分配,且分配的空间大小应该大于结构体的大小,以适应柔性数组的大小


为什么需要柔性数组?

在柔性数组引入之前,我们大可在结构体中定义一个指针变量,再使用这个指针变量维护我们想要的动态内存空间,可以达到和柔性数组一样的效果。

struct S
{
char c;
int* parr;//
};

int main()
{
struct S* p = (struct S*)malloc(sizeof(struct S));//为结构体S开辟空间
if (p != NULL)
{
p->parr = (int*)malloc(10 * sizeof(int));//用结构体中的指针维护空间
if (p->parr != NULL)
{
//使用
}
}

free(p->parr);//注意释放顺序
p->parr = NULL;

free(p);
p = NULL;

return 0;
}


那么柔性数组的存在是多余的吗?答案并不是。如果稍微思考就会发现,上述两种方案的内存结构不尽相同。使用柔性数组时,数组的空间和结构体其他成员的空间都被结构体统一维护;而第二种方案则是在结构体之外又开辟了一块新的内存空间。使用柔性数组只需要进行一次malloc和free操作即可完全释放这块空间,而第二种方案至少需要free两次

C语言动态内存管理_内存管理_03


柔性数组具有以下优势:

​(1)柔性数组方便开辟和释放空间,减少了易错性。

(2)增加了内存利用率。由于柔性数组​的使用一般只需要malloc一次,减少了内存碎片,从而增加内存利用率。

(3)提高了内存访问速度。使用柔性数组时的空间是连续的,提高了内存访问的命中率,从而一定程度提高了内存访问速度。


标签:动态内存,管理,free,C语言,内存,数组,柔性,NULL,指针
From: https://blog.51cto.com/u_15752114/5899860

相关文章

  • 基于SpringBoot+Layui的物业管理系统【完整项目源码】
    使用建议业务逻辑简略,需要细化业务,增加业务开发,如未交费提醒等技术架构数据库:MySQL8.X后端技术:SpringBoot2.3.0,MyBatisPlus数据连接池:Druid前端技术:La......
  • java—不同的用户登录以后可以看到不同的菜单(后台可以实现对用户菜单的管理) 1 (55)
    实现不同的用户登录以后可以看到不同的菜单。(后台可以实现对用户菜单的管理。)第一步:分析数据结构    1:用户表表名:users列名类型说明idVarchar(32)主键nameVarchar(......
  • Systemd 服务管理器
    项目中遇到有些脚本需要通过后台进程运行,保证不被异常中断,变成守护进程的第一步,就是把它改成"后台任务"(backgroundjob)。传统上我们是用以下的方法来做:&只要在命令的尾部加......
  • linux进程管理(一)
    进程介绍程序和进程程序是为了完成某种任务而设计的软件,比如OpenOffice是程序。什么是进程呢?进程就是运行中的程序。一个运行着的程序,可能有多个进程。比如自......
  • Python 依赖库管理哪家强?pipreqs、pigar、pip-tools、pipdeptree 任君挑选
    在Python的项目中,如何管理所用的全部依赖库呢?最主流的做法是维护一份“requirements.txt”,记录下依赖库的名字及其版本号。那么,如何来生成这份文件呢?在上篇文章《​​由浅......
  • 如何管理无服务器计算的安全性
    1、减少无服务器权限无服务器计算的较大风险是具有比所需更多权限的功能。通过无服务器,可以通过为很多已部署的功能实施很小权限模型显著地减少攻击面。在功能的开发阶......
  • 浅谈综合能效管理平台在高校物业管理中的应用
     陈盼摘要 细数物业管理行业快速发展的过程,多业态经营逐渐成为多家企业的重要发展战略。非住宅业态因其市场空间广阔、盈利能力远高于住宅业态,很大程度上吸引物企争相布局......
  • [答疑精选]企业仓储管理与成本核算信息系统愿景(2015/3/2)
    企业仓储管理与成本核算信息系统愿景乡情(382***38)15:11:08 我的题目是:企业仓储管理与成本核算信息系统老大:企业总经理愿景:1. 为工程及时提供所需货品2. 准确核算工程成......
  • 行为管理(锐捷睿易篇)
    大家好,我是小杜,经过近一个月的“艰苦”奋斗将实施部署上网相关的已经学习完了♪(^∇^*),接下来就要转入功能配置类的学习了,加油!!!永远是最棒的!师傅建议可以从行为管......
  • 目录和文件管理
    1.Linux目录1.1Linux文件类型d  目录文件directoryb  块设备文件block块设备文件,就是保存大块数据的设备,比如最常见的硬盘。c  字符设备character这些文......