栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C语言基础语法第八篇

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C语言基础语法第八篇

动态内存管理

1.

栈区:在函数被调用时分配,用于存放函数的参数值,局部变量等值。在windows中默认值是1M,在Linux中栈的默认大小是10M

2.

堆区:程序运行时可以在堆区动态地申请一定大小的内存,并在用完之后归还堆区。在Linux系统中堆区的大小接近3G

一般情况下我们需要大块内存,或程序在运行过程中才知道所需要内存大小,我们就从堆区申请

3.

C中的4个动态内存管理函数:malloc,calloc,realloc,free,都需要引用 stdlib.h,malloc.h

4. 

malloc向堆区申请一块指定大小的连续内存空间

void* malloc(size_t  size)

分配的字节未初始化

若分配成功,则malloc函数返回一个指向该连续空间的无类型指针,该指针内,存放的是连续空间的首地址,也就是其第一个字节的序号

#include 
#include 

int main()
{
	int n = 10;
	int *ip = (int *)malloc(sizeof(int)*n);

	system("pause");
	return 0;
}

若分配失败,则返回为NULL

若size_t为0,则malloc的行为是实现定义的。可返回空指针,也可返回非空指针;但不应当解引用这种指针,而且应将它传递给free,以避免内存泄漏

#include 
#include 
int main()
{
	int *p = (int*)malloc(0);
	system("pause");
	return 0;
}

 malloc申请0个字节,p将会指向下越界标记 

 

 对其进行解引用存储数据将会改变下越界标记,编译器不会报错,但这种是不被允许的

5.

calloc分配并使用0初始化连续的内存空间

void * calloc(size_t num , size_t size); 

为num个元素的数组分配内存,并将所有分配的字节数初始化为0

若分配成功,会返回存储分配空间首地址的无类型指针

若分配失败,则返回NULL

若size为0,则行为是实现定义的,可返回空指针,也可返回不可访问存储空间的指针 

通过上述描述那么我们可以自己实现一下这个calloc函数

#include 
#include 

void* my_calloc(size_t num, size_t size)
{
	void *ptr = (void*)malloc(num*size);
	if (ptr != NULL)
	{
		memset(ptr, 0, num*size);
	}
	return ptr;
}

int main()
{
	int *ip = (int *)my_calloc(10, sizeof(int));
	system("pause");
	return 0;
}

 6. realloc

void* realloc(void *ptr, size_t  new_size);

 重新分配给定的内存区域。它必须是之前malloc,calloc,realloc所分配过的,并且未被free,未被realloc调用所释放的,否则,结果未定义。

重新分配有三种可能:

第一种: 

扩张或收缩ptr所指向的已存在内存,后续未分配空间足够大

 第二种:

 后续未分配内存空间不够大,但堆空间充足

 第三种:

 堆空间不足,则不释放旧内存块,并返回空指针

注:

若重新追加到0,其行为是实现定义的,可返回NULL,此情况下可能或可能不释放旧内存,或返回不会用于访问存储的非空指针

 7.

free用来释放从malloc,calloc,realloc成功获取到动态内存分配的空间

void free (void * ptr)

若ptr为空指针,则函数不进行操作

若ptr的值不是通过从malloc,calloc,realloc返回的值,则行为未定义

若ptr所指向所指向的内存空间已被释放,则行为未定义

若在free()的返回后继续通过ptr访问内存,则行为未定义 (除非另一个分配函数恰好等于ptr的值)

注:

此函数接受空指针(并不对其处理),以减少特列的数量。不管分配是否成功都可将返回的指针传递给free()

free后,只是做了个标记,告诉系统这块内存不用了,如果被free后,指向此空间的指针没被置空,则仍可操作这块空间

free的过程是将原有的空间的数据用ef,ee,或者其他的乱码进行覆盖,以起到保护数据不被泄漏的作用,并将此空间标记为可申请状态

在进行free时,系统会先判断指针所指向堆区空间的状态,第一次free后,已标记为可申请状态,再次free时,系统判断其不是使用状态,而进行报错

#include 
#include 
int main()
{
	int *p = (int *)malloc(sizeof(int));
	*p = 12;
	free(p);
	*p = 23;
	system("pause");
	return 0;
}

 8.

malloc对内存的管理分为大内存和小内存

<128的分配方式

 一小块,一小块的分配,要释放就将一小块整个释放,不能将申请的空间只释放一部分

9.

当程序结束时,如果malloc申请的空间未被释放,则会随着程序结束一起被释放

 10.释放的过程

指向空间首地址的指针的会向上偏移读取头部信息,然后将其申请的空间释放掉,同时为其填充ef,ee,或者dd的乱码

11.对8,9深入代码

#include 
#include 
int main()
{
	int *p = (int *)malloc(sizeof(int)* 10);  // 40个字节
	int *ptr = (int *)malloc(sizeof(int)* 5); // 20个字节
	
	system("pause");
	return 0;
}

未分配ptr时,p所指向空间的内部布局

分配ptr后,p所指向空间的内部布局

ptr所指向的空间内部布局

 

 对比分析:

 12. 堆区和栈区的区别

(1)管理方式:栈由系统自动管理;堆由程序员控制,使用方便,但易产生内存泄漏

(2)生长方向:栈向低地址扩展,是连续的内存区域;堆向高地址扩展,是不连续的内存区域。

这是由于堆区管理系统用链表来存储空闲内存地址,自然不连续,而链表从低地址向高地址遍历

(3)空间大小:栈顶地址和栈的最大容量由系统预先规定(默认1M或10M);堆的大小则受限于计算机系统中有效虚拟内存,32为Linux系统中内存可达2.9G

(4)存储内容:栈在函数调用时,首先压入是函数实参,然后主函数中下条指令的地址压入,最后是被调函数的局部变量。本次调用结束,局部变量先出栈,指令地址出栈,最后栈平衡,程序由该点继续运行下条可执行语句。堆通常在头部用一个字节存放其大小,堆用于生存期与函数调用无关的数据,具体内容由程序员安排

(5)分配方式:栈可静态分配或动态分配。静态分配由编译器完成,如局部变量的分配。动态内存的分配由alloca函数在栈上申请空间,用完后自动释放不需要调用free函数。堆只能动态分配且手动释放

(6)分配效率:栈由计算机底层提供支持:分配专门的寄存器存放栈地址,压栈出栈由专门的指令执行,因此效率较高。堆由函数库提供,机制复杂,效率比栈低得多

(7)分配后系统响应:只要栈剩余空间大于申请空间,系统将为程序提供内存,否则报告异常提示栈溢出。操作系统为对维护一个记录空闲地址的链表。当系统收到程序的内存分配申请时,会遍历该链表寻找第一个空间大于所申请空间的堆结点,然后将该结点从链表的空闲链表中删除,并将该结点空间分配给程序。若无足够大小的空间(可能由于内存碎片太多),有可能调用系统功能去增加程序数据段的内存空间,以便于有机会分到足够大小的内存,然后进行返回。大多数系统会在该内存空间首地址处记录本次分配空间的大小,供后续释放函数准确释放本内存空间。由于找到的堆结点大小不一定正好等于申请大小,系统自动将多余的部分重新放入空闲链表中

(8)碎片问题:栈不会存在碎片问题,因为栈是先进后出的队列,内存块弹出栈之前,在其上面后进栈的内容已弹出。而频繁的申请与释放操作会造成堆内存空间的不连续,从而造成大量碎片,使程序效率低下。

总结:

堆容易造成内存碎片;由于专门的系统支持,效率很低;由于可能引发用户态和内核态切换,内存申请变得格外昂贵。所以栈在程序中应用最广泛,函数调用也利用栈来完成,调用过程中的参数,返回地址,栈基指针和局部变量等都采用栈的方式存放。建议尽量使用栈,仅在分配大量或大块内存空间时,使用堆。最后使用栈和堆时应避免越界发生,否则可能程序崩溃或破环程序堆,栈结构,产生意想不到的结果

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/689926.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号