文章前言:
本文主要介绍C语言中的动态内存管理,主要是4个与之相关的函数。另外,我认为柔性数组也可以归于动态管理中,但其是与结构体密切相关的,建议可以先了解结构体再看柔性数组。
动态内存管理:
为什么会有动态内存管理?
在正式介绍动态内存管理之前,我们应该要先想想为什么需要它?
请看下列代码:
//通讯录
typedef struct person
{
char name[20];
char sex[5];
int age;
char num[15];
char adrs[30];
}person;
int main()
{
//创立通讯录
person A[10] = {0};
}
联合实际上看有没有看出什么问题?
有,而且是很基础的问题——通讯录里面的可以添加的人物太少了!
但有人会说,那直接在代码里初始化一个比较大的数不就可以解决这个问题了么?
很好,但如果这么想的话,我一旦设为1000,甚至1000000,结果在某一段时间用的不过几十余人而已,那又会造成大量的内存浪费,这产生了一个新问题。
因此,我们的主角——动态内存管理 响亮登场!
动态内存分布:
在前言我已经提到动态内存管理是与四个函数有着密切联系,这四个函数的其中三个可以在堆中创建变量。我先给出“内存图”,看看动态内存管理函数在什么地方上大方异彩。
上图是C语言的大概内存分布图,可以看见,动态内存是在堆区上开辟的。
在继续往下介绍前,各位可以先简单想想:动态内存的开辟有什么弊端?
malloc():
函数声明:
void* malloc (size_t size);
可以看出:
1、malloc函数的参数只有一个,那就是size,这个表示要开辟内存有 size个字节大小。
2、malloc函数会返回一个void* 的指针,在使用特定的指针来接收前,必须先将malloc返回的结果强制类型转换。
//针对上面的结构体
person*p = (person*)malloc(5 * sizeof(struct person));
//这里表示创建5个person类型大小的空间
好,这就已经创建完毕了!
让我们接着看下面的函数吧!
calloc():
函数声明:
void* calloc (size_t num, size_t size);
可以看出:
1、calloc函数有两个参数,第一个参数num表示创建多少次,第二个参数表示一次创建多少字节。
2、calloc函数会返回一个void* 的指针,在使用特定的指针来接收前,必须先将malloc返回的结果强制类型转换。这一点与malloc一样。
person*p = (person*)malloc(5 , sizeof(struct person));
华生,发生什么盲点了吗?
与malloc的实现代码相比将呢?
没错,这两个表面上很像!
都是在堆上创建5个struct person大小的空间!
但是它们在具体上有什么区别吗?
有——malloc只是创建空间,而calloc不仅创建空间,还会将里面的数据初始化为0。具体使用什么函数,希望你可以根据情况而来,各有各用途。
realloc():
前面两个函数应该可以很好地创建函数了,那这个函数用来干嘛?
还是看上面代码:
person*p = (person*)malloc(5 * sizeof(struct person));
这时已经开辟好了空间,但我想改变空间大小且保留其中内容,怎么办?
再用上面两个函数就无法实现了吧,这时就需要realloc函数了。
在我的想法中,感觉realloc才是动态内存管理的核心,它是最能体现“变”的函数。
函数声明:
void* realloc (void* ptr, size_t size);
可以看出:
1、realloc函数有两个参数。
第一个参数是一个void指针,用来传递之前已经指向动态内存的指针;
第二个参数是一个整数,表示开辟多少字节大小的空间。
2、realloc函数会返回一个void 的指针,在使用特定的指针来接收前,必须先将realloc返回的结果强制类型转换。这一点与malloc和calloc一样。
person* p1 = (person*)realloc(p, 10 * sizeof(person));
//变大了,而且没改变原有的数据
函数大致实现逻辑:
realloc函数在执行时根据之后的空间大小和改变后的大小有两种执行方案。
1、如果原有空间之后有足够大的空间
那么就直接添加就好。
2、原有空间之后没有足够大的空间
malloc会向其它充足空间申请开辟(一定是成功开辟),并会将原数据都复制到新空间去,最后会返回这个空间的起始地址并销毁原空间。
free():
前三个函数已经可以很好地开辟动态内存空间了,那这个函数是做甚???
这个函数可以说是上面三个函数的伴生兄弟!在使用上面的三个函数后,如果你不想用已经开辟好了的空间,请一定要销毁原空间,尤其是在函数中创建的空间,一定要用指针保存地址或者销毁。
话说得这么多,你应该知道这个函数就是用来释放空间的。
person*p2 = (person*)malloc(5 * sizeof(struct person));
//想要实现的具体代码
//
//
//
//不想要这块内存了
//释放吧
free(p2);
//请一定要把原本指向动态内存的指针设为空指针,否则该指针就是野指针,不知道指向内存那一块
p2 = NULL;
上述代码是使用动态内存管理的大致步骤,当然可以有不同的写法,不过这种应该最简单且直接的了。
柔性数组:
什么是柔性数组?
结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。
struct list
{
int a;
char b[];
//char b[0]
//这里的[0]和上面的[]一样,表示未知数组大小
//因此b也就是我们所说的柔性数组
}
柔性数组的特点:
1、结构中的柔性数组成员前面必须至少一个其他成员。
2、sizeof 返回的这种结构大小不包括柔性数组的内存。
3、包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
第一条好懂,下面看第二、三条规则。
//第二条
typedef struct A
{
int i;
int a[0];//柔性数组成员
}A;
printf("%d\n", sizeof(A));
//输出的是4
//也就是说,sizeof操作符没有考虑柔性数组的大小,因为其根本未知,需要动态创建
//第三条
int i = 0;
A *p = (type_a*)malloc(sizeof(A)+100*sizeof(int));
//注意这里参数写的格式:结构体大小 + 开辟的柔性数组的大小
柔性函数的使用:
//承接上述代码
for(i=0; i<100; i++)
{
p->a[i] = i;
}
free(p);
这样柔性数组不仅有了100个整形空间,还以递增的形式从0开始对空间进行赋值。