###C语言神话的动态内存管理不过就是四个函数?快进来自测一下这些基础知识是否完全掌握/小众知识点**柔性数组** <动态内存管理,malloc,calloc,realloc,free>


引入

YY同学有一个神秘的活页本,用来记录每天发生事情,随着时间推移,活页本的纸张显得不够用了,YY同学买了活页纸补充进去。

int main()
{
	int n;//在栈区开辟四个字节的空间
	int arr[10];//在栈区开辟四十个字节的空间
	return 0;
}

在我们以往的代码中,开辟空间的方式只有上述两种,其特点是

  • 空间开辟⼤⼩是固定的。
  • 数组在申明的时候,必须指定数组的⻓度,数组空间⼀旦确定了⼤⼩不能调整

但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间⼤⼩在程序运⾏的时候才能知道,像数组那样开辟空间的⽅式就不能满⾜需求了。于是,C语言引入了动态内存管理,让程序员可以申请和释放空间

malloc && free

malloc

从C语言官网可以了解到,malloc函数原型为void* malloc (size_t),头文件为stdlib.h,参数为size_t类型,返回值为void*。解释:参数为申请的空间大小,返回值为开辟空间的起始地址。

int main()
{
	//申请十个整形的空间
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		perror("malloc");//如果申请失败,打印失败原因
		return 1;
	}
	return 0;
}

注意事项

  • 如果申请成功,则返回开辟空间的起始地址
  • 如果申请失败,则返回一个空指针,因此,在使用malloc函数时,一定要判断返回值是否为空
  • 返回值的类型由程序员自己决定

free

C语⾔提供了另外⼀个函数free,专⻔是⽤来做动态内存的释放和回收的,函数原型如下:void free(void*)
简单理解就是传入一个地址(动态开辟的空间)将其释放

int main()
{
	int* p = (int*)malloc(10*sizeof(int));
	if(p == NULL)
	{
		perror("malloc");
		return 1;
	}
	free(p);
	p = NULL;
	return 0;
}

需要注意的是此时p中仍然存放着先前的地址,为野指针,需要将其赋值为NULL

YY:用malloc申请的空间有啥特别的呢?

  • 空间大小可以改变
  • 在堆区上申请
  • 需要手动的回收空间,释放内存,防止内存泄漏

malloc与free一般成对出现

calloc && realloc

calloc

calloc函数与malloc函数类似,作用是开辟空间并且将内容赋值为0,其函数原型为void *calloc(size_t num,size_t size)第一个参数为向内存申请类型的个数,第二个参数为向内存申请的类型大小

int main()
{
	int* p = (int*)calloc(10, sizeof(int));
	if (p == NULL)
	{
		perror("calloc");
		return 1;
	}
	free(p);
	p == NULL;
	return 0;
}

需要初始化用calloc,不需要初始化用malloc

realloc

在写代码的过程中,多数情况下我们不能一次性开辟完全合适的空间,需要不断地调整,追加空间或者缩小空间,这时就需要用到realloc函数
realloc函数是最符合动态内存管理的函数,作用是在原有动态开辟的内存的基础上继续开辟空间,其函数原型为void* realloc (void* ptr, size_t size); 第一个参数是指向一个被动态开辟好的内存块,第二个参数为想要开辟的新的大小,返回值为调整之后的内存的起始位置

int main()
{
	int* p = (int*)calloc(10, sizeof(int));
	if (p == NULL)
	{
		perror("calloc");
		return 1;
	}
	int* ptr = (int*)realloc(p, 20 * sizeof(int));
	if (ptr!= NULL)
	{
		p = ptr;
	}
	free(p);
	p = NULL;
	return 0;
}

这里需要注意realloc使用过程中的三种情况

  • 后续空间充足,直接向后追加空间,并且返回旧地址
  • 后续空间不足
    • 在堆区找一块大小足够的空间
    • 将原先的数据拷贝进来
    • 释放旧的空间
    • 返回新的地址
  • 扩容失败,返回空指针NULL

这里不可用p指针来直接接收扩容后的地址,因为有可能会扩容失败,导致原有的数据丢失,所以创建一个新的指针来接收realloc的返回值,若返回值不为空,则赋值给p

常见的动态内存的错误

对空指针的解引用操作

void test()
{
	int* p = (int*)malloc(INT_MAX / 4);
	*p = 20;//如果p的值是NULL,就会有问题
	free(p);
}

错误原因:没有对p进行判空,容易导致对空指针解引用
改正为:

void test()
{
	int* p = (int*)malloc(INT_MAX / 4);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	*p = 20;//如果p的值是NULL,就会有问题
	free(p);
}

对非动态开辟的内存使用free释放

void test()
{
	int a = 0;
	int* p = &a;
	free(p);
}

再次强调–>free只能释放动态开辟的内存空间

使⽤free释放⼀块动态开辟内存的⼀部分

int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	for (int i = 0; i < 5; i++)
	{
		*p = i;
		p++;//err
	}

	free(p);
	p = NULL;
	return 0;
}

此时p已经不再指向起始位置,所以释放失败
改正为:

int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	for (int i = 0; i < 5; i++)
	{
		p[i] = i;
	}

	free(p);
	p = NULL;
	return 0;
}

对同一块空间多次释放

void test()
 {
	 int *p = (int *)malloc(100);
	 free(p);
	 free(p);//重复释放
 }

动态开辟内存但忘记释放(内存泄漏)

void test()
{
	int flag = 1;
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		return;
	}

	if (flag)//err
	{
		return;
	}

	free(p);
	p = NULL;
}
int main()
{
	test();
	//....
	return 0;
}

上述代码虽然写了free函数,但并未执行,导致申请的内存没有被释放掉,最终造成内存泄漏

柔性数组

柔性数组引入

struct S
{
	int n;
	int arr[];
};

以上代码我们声明了一个结构体S,仔细观察发现结构体中的最后一个成员为整形数组,但是又和普通的数组有差别,其没有规定数组的大小,我们称这样的数组为柔性数组

柔性数组的特点

  • 结构中的柔性数组成员前⾯必须⾄少⼀个其他成员。
  • sizeof 返回的这种结构⼤⼩不包括柔性数组的内存。
  • 包含柔性数组成员的结构⽤malloc ()函数进⾏内存的动态分配,并且分配的内存应该⼤于结构的⼤⼩,以适应柔性数组的预期⼤⼩。

解释:第二条就是说,在用sizeof计算结构体大小时,不会将柔性数组的大小计算在内。第三条是说,应该动态分配足够的大小,以提供给柔性数组使用

柔性数组代码举例

struct S
{
	int n;
	int arr[];
};
int main()
{
	struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));//动态申请内存
	if (ps == NULL)
	{
		perror("malloc");
		return 1;
	}

	ps->n = 10;//赋值
	for (int i = 0; i < 10; i++)
	{
		ps->arr[i] = i + 1;
	}

	for (int i = 0; i < 10; i++)//使用
	{
		printf("%d ", ps->arr[i]);
	}
	printf("\n");

	struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 20 * sizeof(int));//扩容
	if (ptr != NULL)//检验是否扩容成功
	{
		ps = ptr;
	}
	else
	{
		return 1;
	}
	
	for (int i = 0; i < 20; i++)//使用
	{
		printf("%d ", ps->arr[i]);
	}

	free(ps);//释放内存
	ps = NULL;

	return 0;
}

通过realloc函数就可体现出柔性数组的大小是可以改变的

内存区域划分

C语言中将内存大体分为五个区域,下以表格的形式展示

区域名称功能及特点
内核空间准们为操作系统开辟的一块空间,用户不可读写
栈区在执⾏函数时,函数内局部变量的存储单元都可以在栈上创建,函数执⾏结束时这些存储单元⾃动被释放。栈内存分配运算内置于处理器的指令集中,效率很⾼,但是分配的内存容量有限。 栈区主要存放运⾏函数⽽分配的局部变量、函数参数、返回数据、返回地址等
堆区⼀般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配⽅式类似于链表
静态区也叫数据段,存放全局变量、静态数据。程序结束后由系统释放
代码段存放函数体(类成员函数和全局函数)的⼆进制代码

总结

以上是C语言中动态内存管理相关的知识,我们要像捡贝壳一样捡到自己的小篮子里哦,有不明白的地方欢迎留言,作者水平有限,文章不妥部分还请各位读者指正。

  • 19
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值