【进阶C语言】动态内存管理+柔性数组

文章目录

    • 1.动态内存的开辟
      • 内存
      • 程序空间的布局
      • 内存池
      • 内存碎片
      • 内存泄漏
    • 2.动态内存函数
      • malloc
        • 功能
        • 函数
      • calloc
        • 功能
        • 函数
      • realloc
        • 功能
        • 函数
        • 开辟时遇到的两种情况
      • free
        • 功能
        • 函数
    • 3.  建议
    • 4.柔性数组
      • 特性:
      • 定义
      • 使用
      • 优点

1.动态内存的开辟

内存

用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器交换的数据。它是外存与CPU进行沟通的桥梁,计算机中所有程序的运行都在内存中进行,内存性能的强弱影响计算机整体发挥的水平。
补充:外存:又称为辅存,是指除计算机内存及CPU缓存以外的存储器,此类存储器断电后仍能保存数据。常见的外存有硬盘、光盘、U盘等。CPU如果想访问外存中的数据,必须先把外存的数据保存到内存中,CPU再去读取内存中的数据。

程序空间的布局

【进阶C语言】动态内存管理+柔性数组_第1张图片
我们常用的内存开辟函数都是在堆区开辟的——malloc,calloc,realloc,free。

内存池

  1. 定义
     内存池(Memory Pool)是一种内存分配方式。
  2. 优点
     内存池则是在真正使用内存之前,预先申请分配一定数量、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的一个显著优点是,使得内存分配效率得到提升

注意:这里的提升是一定程度上的,不一定效率提升很大。

内存碎片

定义
 内存碎片即“碎片的内存”描述一个系统中所有不可用的空闲内存,这些碎片之所以不能被使用,是因为负责动态分配内存的分配算法使得这些空闲的内存无法使用,这一问题的发生,原因在于这些空闲内存以小且不连续方式出现在不同的位置
简单来说:就是一些比较小且无法进行利用的内存空间。

内存泄漏

定义:
 内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

程序结束时,会将占用的内存还给操作系统,但是如果有一个不停运行的程序呢?如果在运行时所占用的内存空间无法及时的释放,则可能在一段时间后,程序死机或者运行缓慢,这就是我们电脑死机重启恢复正常的原因。那如果我们将用完的内存及时的还给操作系统,这样我们的程序,在理论上,具有了持续运行的能力。

2.动态内存函数

malloc

功能

向内存申请一块连续可用的空间,并返回指向这块空间的指针。

返回的指针建议强制类型转换为所使用指针的类型,因为返回的类型是void*的指针,容易报出警告。

函数

1.返回类型:void*
2.参数
要开辟的字节个数——size_t

注意:
1.开辟失败,则返回一个空指针,空指针是不能被解引用的,要进行检查以免出错。
2.开辟成功,则返回指向该空间首位置的指针。
3.如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器,但是返回的地址不应该被解引用。
4.用完的函数要及时的还给操作系统。

calloc

功能

与malloc的功能相似,向内存申请一块连续可用的空间,并且设置初始值,并返回指向这块空间的指针。

函数

1.返回类型:void*
2.参数

1.要开辟的类型个数——size_t
2.要开辟的类型字节个数——size_t
3.其它的注意事项与malloc基本相同
4.由于需要初始化所以calloc要比malloc的效率稍微低一点

realloc

功能

不够使用的内存空间重新开辟,进行增容。

说明:
如果没有空间,也能进行扩容。相当于malloc。

函数

1.返回类型:void*
2.参数

1.要调整的内存空间的地址——void*
2.要申请的比原先更大的字节数——size_t

开辟时遇到的两种情况

【进阶C语言】动态内存管理+柔性数组_第2张图片

这个函数考虑的很周到:
情况一:返回的是原先的地址
情况二:将原先不够的空间进行释放,还给操作系统,,同时返回的是开辟好的空间的地址。

free

功能

将在堆区开辟的已经使用过的空间返还给操作系统,经常与上面的内存开辟函数进行搭配着使用。

注意:
1.其它区上可不能用free
2.free过后的空间指针并没有被置为空指针,要手动置为空指针
3.在传入NULL时,free啥也不干。
4.对已经释放的空间的起始位置再一次释放会报错,所以要及时置为空指针。

函数

1.返回类型:void
2.参数
要释放空间位置的起始地址——void*

3.  建议

1.对接收的指针要进行检查,防止其为空指针
2.对释放后的指针所指向的空间后,将指针及时的置为NULL,防止报错。
3.free的范围仅限在堆区所开辟的空间使用
4.注意使用指针的越界问题
5.动态内存函数的开辟尤其是在函数内部开辟时,要及时的在函数内部置为空指针,否则这块开辟的空间将无法进行正常的使用,会导致内存泄漏的问题。

4.柔性数组

特性:

 它允许你在定义结构体时创建一个空数组,而这个数组的大小可以在程序运行的过程中根据你的需求进行更改特别注意的一点是,并且还要求这样的结构体至少包含一个其他类型的成员,这个空数组必须声明为结构体的最后一个成员

注意:
1.在结构体里面才能出现柔性数组
2.结构中的柔性数组成员前面必须至少一个其他成员。
3. 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
4.这个空数组必须声明为结构体的最后一个成员

定义

法1:

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

法2:

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

使用

typedef struct st_type
{
int i;
int a[];//柔性数组成员
}type_a;
int main()
{
	int i = 0;
	type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));
	//如果不够使用realloc进行增容
	for(i=0; i<100; i++)
	{
	  p->a[i] = i;
	}
	free(p);
	p=NULL;
	return 0;
}

优点

看与之功能相似的代码:

typedef struct st_type
{
	int i;
	int *p_a;//指针可以指向开辟空间的地址
}type_a;//此结构体的大小是固定的——32位8字节/64位12字节
int main()
{
	type_a *p = (type_a *)malloc(sizeof(type_a));
	p->i = 100;
	p->p_a = (int *)malloc(p->i*sizeof(int));
	return 0;
}

为了与柔型数组的功能靠拢,我们将空间都开辟在堆区上。
接下来我们画图来看这个代码:
【进阶C语言】动态内存管理+柔性数组_第3张图片
再看一下柔性数组的实现图解:
【进阶C语言】动态内存管理+柔性数组_第4张图片
对比看:

区别:一个是一块一块的开辟,另一个是整块的开辟。
联想到内存池和内存碎片的概念,我们会发现柔型数组与内存池的概念类似,占用一整块空间,会在一定程度上提高内存的使用效率,而模拟的那个会导致内存碎片的不断积累从而会降低内存的利用效率。
并且在内存释放的时候一个释放两次,这样可能会导致忘记释放的毛病,从而导致内存泄漏,而柔型数组只需释放一次,这样会使代码的使用效率变高。

你可能感兴趣的:(进阶C语言,c语言,柔性数组,c#)