【C语言动态内存管理】(详细完整版)

1.为什么要有动态内存

以下又两种内存开辟的方式

int val = 20;//在栈空间上开辟四个字节
char arr[10] = {
    
    0};//在栈空间上开辟10个字节的连续空间

特点:

  • 空间开辟大小是固定的。
  • 数组在申明的时候,必须指定数组的长度,数组空间一旦确定了大小不能调整。
    但有时候程序在运行的时候才知道所需空间的大小,所以引用了动态内存,可以让程序员自己灵活地申请和释放空间。

2.malloc和free

malloc和free都声明在 stdlib.h 头文件中。

2.1malloc

C语言提供了⼀个动态内存开辟的函数:

void* malloc (size_t size);

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

  • 如果开辟成功,则返回⼀个指向开辟好空间的指针
  • 如果开辟失败,则返回⼀个 NULL 指针,因此malloc的返回值⼀定要做检查。
  • 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
  • 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
#include<stdio.h>
#include<stdlib.h>
int main()
{
    
    
	int* ptr = NULL;
	ptr = (int*)malloc(10 * sizeof(int));
	if (ptr == NULL)
	{
    
    
		perror("malloc");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
    
    
		*(ptr + i) = i + 1;
	}

	for (i = 0; i < 10; i++)
	{
    
    
		printf("% d", *(ptr + i));
	}
	
	free(ptr);
	ptr == NULL;
	return 0;
}

2.2free

C语言提供了另外⼀个函数free,专门是用来做动态内存的释放和回收的,函数原型如下:

 void free (void* ptr);

free用来释放动态开辟的内存。

  • 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
  • 如果参数 ptr 是NULL指针,则函数什么事都不做。

3.calloc和realloc

3.1calloc

C语⾔还提供了⼀个函数叫 calloccalloc 函数也⽤来动态内存分配。原型如下:

void* calloc (size_t num, size_t size);
  • 函数的功能是为 num 个大小为 size 的元素开辟⼀块空间,并且把空间的每个字节初始化为0。
  • 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。

3.2realloc

有了realloc可以让内存的开辟更加灵活。
函数原型如下:

void* realloc (void* ptr, size_t size);
  • ptr 是要调整的内存地址
  • size 调整之后新大小
  • 返回值为调整之后的内存起始位置。
  • 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。
  • 还会存在两种情况,原地扩容和异地扩容。

4.柔性数组

C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员

4.1柔性数组的特点

  • 结构中的柔性数组成员前面必须至少一个其他成员。
  • sizeof 返回的这种结构大小不包括柔性数组的内存。
  • 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该⼤于结构的大小,以适应柔性数组的预期大小。

4.2柔性数组的使用

#include<stdio.h>
#include<stdlib.h>
struct S
{
    
    
	int n;
	int arr[];
};
int main()
{
    
    
	struct S* str = (struct S*)malloc(sizeof(struct S) + 20);
	int i = 0;
	str->n = 100;
	for(i = 0; i < 5; i++)
	{
    
    
		str->arr[i] = i + 1;
		printf("%d", str->arr[i]);
	}
	free(str);
	return 0;
}

4.3柔性数组的优点

第一个好处是:方便内存释放
第⼆个好处是:这样有利于访问速度。
连续的内存有益于提高访问速度,也有益于减少内存碎片。