万字博客详解C语言中的动态内存管理_c 进程每2秒钟会分配一个10000字节的空间,并作简单赋值(1)

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

calloc和malloc对比:
malloc 只有一个参数假如为40表示开辟40个字节空间
colloc有两个参数(10,4)表示开辟10个4字节的空间即10*4为40个字节空间,二者最后开辟的空间字节个数是一样的
malloc开辟的空间最后不会初始化 是随机值
calloc开辟的空间每个内存单元都会初始化为0…

#include<stdio.h>
#include<stdlib.h>
int main()
{//返回的是void\* 可以强转类型后赋给对应类型变量也可以直接赋值最后都会根据变量的类型自身转换为该类型赋值
	int\* p = calloc(10, sizeof(int));
	int\* l = malloc(40);
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", \*(p + i));
		
		printf("%d \n", \*(l + i));
	}
	return 0;
}

在这里插入图片描述

可以看到calloc 和malloc都开辟了40个字节空间,最后都以整形输出10个整形元素 calloc里为10个0 而malloc是随机值

3.realloc动态内存分配函数

右边为realloc动态内存分配函数官方文档->realloc

realloc函数的出现让动态内存管理更加灵活。
有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时
候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小
的调整。

在这里插入图片描述

realloc库函数返回类型是void*
第一个形式参数ptr为void*类型,接受的是要重新开辟的空间的起始地址
第二个参数是size_t 表示无符号整数 表示要重新开辟的空间大小
如果第一个参数接受的是NULL则表示在堆区开辟第二参数表示的字节个数的空间等同于malloc

realloc主要作用就是是原先开辟的空间动态减少或者动态增长,达到动态变化的效果
使原来已有的空间减少时传递的字节个数比原来空间小,此时会减少原来空间,减少的空间数据被丢失…最后返回当前被减少后的内存空间的指针…

在这里插入图片描述

realloc是原来空间增加传递的字节个数比原来空间大,此时有两种情况
当给当前空间增长时,后面能容纳增长的区域,直接在当前空间后面加要增长的字节空间个数,返回当前空间起始地址,即原空间起始地址
当给当前空间增长时.后面有其他空间存在或者后面已经到了堆区范围,此时会重新在堆区找一块能容纳增长后所有字节大小的区域开辟一块空间,将原来空间的数据拷贝到当前空间并释放掉原来空间,返回当前被开辟的空间起始地址

在这里插入图片描述

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int\* p = (int\*)realloc(NULL, 20);
	if (p == NULL)
	{
		printf("开辟失败\n");
			return 1;
	}
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		p[i] = i;
		printf("%d ", p[i]);
	}
	printf("\n");
	p = (int\*)realloc(p, 12);   //重新开辟12个字节空间 表示在原来空间上减少后面8字节空间
	for (i = 0; i < 3; i++)
	{
		p[i] = i;
		printf("%d ", p[i]);
	}
	printf("\n");
	int\* tmp = (int\*)realloc(p, 40);  //重新开辟40个字节空间,在原来空间基础上动态增长20个字节空间可能开辟失败会返回NULL ,先用一个临时变量接受
	if (tmp == NULL)
	{
		printf("增容失败\n");
		return 1;
	}
	p = tmp;
	for (i = 0; i < 10; i++)
	{
		p[i] = i;
		printf("%d ", p[i]);
	}
	return 0;
}

在这里插入图片描述

上面为测试realloc一个参数为NULL指针时,相当于malloc函数开辟后面20个字节空间
将返回的起始地址转换为整形指针,即将20个字节空间看成5个整形空间,分布赋值后输出
用realloc(p,12)表示重新开辟12个字节,等价于将p指针指向的动态内存空间动态减少后面8字节空间,得到12个字节空间,后面8字节空间数据丢失,前面的仍保留,实现了动态减少的效果

realloc(p,40)表示重新开辟40个字节空间,等价于都p指针指向的动态内存空间往后增长到40个字节空间,可能出现增长时容量已满增容失败返回NULL
此时先用一个临时指针变量接受返回的指针,经过判断不是NULL指针后将其赋值给p
此时p指针得到的是指向40个字节空间的指针,增长前空间的数据仍然会被保留,实现了动态增长的效果

4.free释放动态内存空间函数

右边数free库函数的官方文档->free

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

在这里插入图片描述

free 形参类型为void* 表示接受的是一个指向需要释放的动态空间的指针,
返回类型是void表示无返回值

free是一个很重要的函数, 我们知道在栈区申请的局部空间,在出其局部范围时空间会被释放,
而堆区申请的空间,只有在整个程序结束时才会被释放,而往往有时候我们只需要临时开辟空间,用完就不再使用了,而不使用这个空间但是仍然在程序里就会浪费不必要的空间
需要做到在程序运行时释放空间,就要用到free函数

#include<stdio.h>
#include<stdlib.h>
void print()
{
	int\* p = (int\*)malloc(40);
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		p[i] = i;
		printf("%d ", p[i]);	
	}
}
int main()
{
	
	print();
	return 0;
}

当我们在一个自定义函数里使用动态内存函数在堆区申请空间进行某些操作时,如上面代码使用malloc在堆区申请40个字节空间然后当做十个整形元素赋值并遍历打印
执行完这个函数后,显然这些空间我们已经不需要再使用了,而它并不会像数组一样出了这个函数空间被释放

堆区申请的空间要在整个程序结束后才会被自动释放还给操作系统,在上面代码运行完函数后最后结束程序会释放空间
但实际以后编写的程序都是在服务器之类上运行,而服务器是二十四小时不间断运行的,不会结束程序,而随着程序运行实现某些功能不停在堆区申请空间而释放不了,最后会造成空间浪费很多会导致服务器崩溃

要使程序在运行过程中释放已经申请开辟了的且不需要使用的堆区空间就需要用到free函数

#include<stdio.h>
#include<stdlib.h>
void print()
{
	int\* p = (int\*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");   //perror打印错误信息函数 
		return 1;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		p[i] = i;
		printf("%d ", p[i]);
	}
	free(p);    //p为指向堆区开辟的空间的起始地址 传参调用free函数释放这个开辟的空间
	p = NULL;   //释放后p里的指针指向的未知空间是野指针 为了避免对野指针解引用将p置为NULL
}
int main()
{
	
	print();
	return 0;
}

上面代码加了free函数,参数是指向堆区申请开辟的空间的起始地址,作用是将传递的指针指向堆区的空间释放还给操作系统,此时就实现了在程序运行中释放掉堆区申请开辟的空间
注意:p指向的已开辟堆区空间被释放后,p指针此时指向的一块未知空间是一个野指针,为了避免再对p野指针解引用操作将p置为NULL

五.常见的动态内存错误

在堆区申请开辟动态内存比栈区的静态内存更为灵活,但是使用它需要更谨慎,使用不当会出现很多内存错误,要合理使用并管理动态内存,得认识常见的动态内存错误

1.对free后指针的解引用操作

void test()
{
int \*p = (int \*)malloc(INT_MAX/4);
free(p);  //free后p指向的空间被释放

\*p = 20;//此时p指针属于野指针,解引用赋值就会有问题


申请一块动态内存得到指向该内存的指针 后,通过指针free释放掉这个内存空间后,这个指针变为了野指针,此时不小心再对野指针解引用操作是错误的…
free后不能再对free后的空间的指针进行解引用操作

2.对动态开辟空间的越界访问

void test()
{
int i = 0;
int \*p = (int \*)malloc(10\*sizeof(int));
if(NULL == p)
{
   perror("test");
   return 1;
}
for(i=0; i<=10; i++)
{
\*(p+i) = i;//当i是10的时候越界访问
}
free(p);
}

上面代码开辟了40个字节的空间,分为10个4字节的整形空间访问,但通过循环访问时i为10访问到了第11个整形空间,属于越界访问,是错误的…
访问开辟的空间要注意不要发生越界访问

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

void test()
{
int a = 10;
int \*p = &a;
free(p);//ok?

上面代码 在栈区创建了一个整形空间 ,取到整形空间地址给了整形变量p ,通过传p给free释放p指向的空间,而p指向的不是堆区的动态空间,会出错…
不能对非动态内存空间进行free

4.使用free释放一块动态开辟内存的一部分

void test()
{
int \*p = (int \*)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}

上面代码开辟了100个字节的空间,返回起始地址给了p, 运行了p++,此时p指针是指向malloc开辟的空间第二个整形位置,即变为了偏移量4的地址, 将p传给free表示从当前偏移量4的位置往后释放空间,而前面4个字节的空间未被释放,是错误的…
不能free动态内存的一部分,要从起始地址开始往后free

5.对同一块动态内存多次释放

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

上面代码 开辟了100个字节空间 ,返回起始地址给了p,然后free§将开辟的空间释放后,又运行了free§此时p属于野指针,再次free是错误的…
不能对同一块动态内存空间多次释放

6.动态开辟内存忘记释放(内存泄漏)!!!

void test()
{
int \*p = (int \*)malloc(100);
if(NULL != p)
{
\*p = 20;
}
}
int main()
{
test();
while(1);
}

上面代码在test函数里动态开辟了100个字节的空间,对该空间进行访问操作后结束函数,
进入了一个死循环,这个死循环就类比服务器程序,当一个程序不间断运行时,而之前开辟的动态内存因为使用完后没有及时free掉,结束函数后记录该空间的指针变量也以为函数结束而被释放,此时再也无法找到指向该动态内存的指针,而该内存也没有被释放,将一直残留在程序里也使用不了,造成严重错误,这个错误也被称作内存泄漏
动态开辟的空间一定及时free

7.在死循环里动态开辟内存空间

#include<stdio.h>
int main()
{
	while (1)
	{
		malloc(10);
	}
	return 0;
}

当我们在一个循环里,动态开辟空间,随着循环次数越多,也就可能导致运行开辟动态内存空间函数越多,在堆区开辟的空间也就越多,此时还没有对其free的话,很有可能使电脑内存占满而崩溃!!!
上面代码写的一个死循环,下面看看在死循环里不断动态开辟内存里是什么情况

在这里插入图片描述

上面是没有运行时的截图,先打开任务管理器ALT+Ctrl+Delete查看内存情况是正常的
下面冒着死机风险运行了程序

在这里插入图片描述

可以看到才运行了一会儿,电脑内存就已经使用率到了94%!!!,可以看到,不free空间,又频繁动态开辟空间的后果,最后可能会导致电脑死机!!

六.柔性数组

也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。
C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。

1.柔性数组的特点

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

2.柔性数组的开辟方式

typedef struct st\_type
{
int i;
int a[];//柔性数组成员
}type_a;

有些编译器会报错则可以改成:

typedef struct st\_type
{
int i;
int a[0];//柔性数组成员
}type_a;

根据柔性数组特点可以看出这种开辟方法一开始数组是没有元素个数即的,使用sizeof 求出来的也是柔性数组以外的成员大小

#include<stdio.h>
struct S
{
	int i;
	int arr[];
};
int main()
{
	printf("%d", sizeof(struct S));//输出结果是什么
	return 0;
}

根据特点,结构体最后一个成员变量为int arr[]未指定数组元素个数是一个柔性数组,此时求struct S的大小是除柔性数组以外的其他成员变量(再考虑内存对齐),最后结构大小为4字节

在这里插入图片描述

3.柔性数组的使用

柔性数组是没有元素个数的,而它的特性就是可以通过动态内存函数使其类似于获得自身的元素个数即内存空间,也可以通过重置动态内存函数改变其后面访问元素个数,实现数组空间动态增长!!!

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化资料的朋友,可以戳这里获取

t S的大小是除柔性数组以外的其他成员变量(再考虑内存对齐),最后结构大小为4字节

在这里插入图片描述

3.柔性数组的使用

柔性数组是没有元素个数的,而它的特性就是可以通过动态内存函数使其类似于获得自身的元素个数即内存空间,也可以通过重置动态内存函数改变其后面访问元素个数,实现数组空间动态增长!!!

[外链图片转存中…(img-w67pcFxy-1715376851890)]
[外链图片转存中…(img-0lfaFYm4-1715376851891)]
[外链图片转存中…(img-BJPZmjCZ-1715376851891)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化资料的朋友,可以戳这里获取

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值