1.栈区
栈区在函数被调时分配,用于存放函数的参数值,局部变量等值。
2.堆区
程序运行时可以在堆区动态地请求一定大小的内存,并在用完之后归还给给堆区。
一般情况下我们需要大块内存或程序在运行的过程中才知道所需内存大小,我们就从堆区分配空间。
3.动态内存分配函数
C语言中动态内存管理的有四个函数:malloc,calloc,realloc,free,都需要引用stdlib.h或malloc.h文件。
malloc向堆区申请一块指定大小的连续内存空间。
void* malloc(size_t size);
分配size字节的未初始化内存。
若分配成功,则返回为任何拥有基础对齐的对象类型对齐的指针。
若size为零,则malloc的行为是现实定义的。例如可返回空指针。亦可返回非空指针。但不应当解引用这种指针,而且应当将它传递给free以避免内存泄漏。
- malloc是线程安全的:它表现得如同只访问通过其参数可见的内存区域,而非任何静态内存。
- 令free或realloc归还一块内存区域的先前调用,同步于令malloc分配相同或部分相同的内存区域的调用。此同步出现于任何通过解分配函数所作的内存访问后,和任何malloc所作的内存访问前。所有操作每块特定内存区域的分配和解分配函数有单独全序。
- 参数
size要分配的字节数 - 返回值
成功时,返回指向新分配内存的指针。为避免内存泄漏,必须用free()或realloc()解分配返回的指针。
失败时,返回空指针。 - free用来释放从malloc,realloc,calloc成功获取到的动态内存分配的空间。
void free(void* ptr);
若ptr为空指针,则函数不进行操作。
若ptr的值不是之前从malloc()、calloc()、relloc()或aligined_alloc()返回的值,则行为未定义。
若ptr所指代的内存区域已经被解分配,则行为未定义,即是说已经以ptr为参数调用free()或realloc(),而且没有后续的malloc()、calloc()或realloc()调用以ptr为结果。
若在free()返回后通过指针ptr访问内存,则行为未定义(除非另一个分配函数恰好返回等于ptr的值)。 - free是线程安全的:它表现得如同只访问通过其参数可见的内存区域,而非任何静态存储。
令free解分配内存区域的调用,同步于任何令分配函数分配相同或部分相同区域的后续调用。这种同步出现于任何解分配函数所做的内存访问后,和任何分配函数所做的访问前。所有操作每块特定内存区域的分配及解分配函数拥有单独全序。 - 参数
ptr指向要解分配的内存的指针。 - 返回值(无)
*此函数接收空指针(并对其不处理)以减少特例的数量。不管分配成功与否,分配函数返回的指针都能传递给free(). - 使用memset函数:将内存单元置为0.
3.2堆区与栈区的区别
(1)管理方式;栈由系统自动管理;堆由程序员自己控制,使用方便,但易产生内存泄漏。
(2)生长方向:栈向低地址扩展(向下生长),是连续的内存区域;堆向高地址扩展(向上生长),是不连续的内存区域。只是由于堆区管理系统用链表来存储空闲内存地址,自然不连续,而链表从低地址向高地址遍历。
(3)空间大小:栈顶地址和栈的最大容量由系统预先规定;堆的大小则受限于计算机系统中有效的虚拟内存,32位Linux系统中堆内存可达2.9G空间。
(4)存储内容:栈在函数调用时,首先压入是函数实参,然后主调函数中下条指令(函数调用语句的下条可执行语句)的地址压入,最后是被调函数的局部变量。本次调用结束后,局部变量先出栈,指令地址出栈,最后栈平衡,程序由该点继续运行下条可执行语句。堆通常在头部用一一个字节存放其大小,堆用于存储生存期与函数调用无关的数据,具体内容由程序员安排。
(5)分配方式:栈可静态分配或动态分配。静态分配由编译器完成,如局部变量的分配。动态分配由alloca函数在栈上申请空间,用完后自动释放不需要调动free函数。堆只能动态分配且手工释放。
(6)分配效率:栈由计算机底层提供支持:分配专门的寄存器存放栈地址,压栈出栈由专[的指令执行,因此效率较高。堆由函数库提供,机制复杂,效率比栈低得多。刀)分配后系统响应:只要栈剩余空间大于所申请空间,系统将为程序提供内存,否则报告异常提示栈溢出。操作系统为堆维护一个记录空闲内存地址的链表。当系统收到程序的内存分配申请时,会遍历该链表寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点空间分配给程序。若无足够大小的空间(可能由于内存碎片太多),有可能调用系统功能去增加程序数据段的内存空间,以便有机会分到足够大小的内存,然后进行返回。大多数系统会在该内存空间首地址处记录本次分配的内存大小,供后续的释放函数(如free/delete)正确释放本内存空间。此外,由于找到的堆结点大小不-定正好等于申请的大小,系统会自动将多余的部分重新放入空闲链表中。
(8)碎片问题:栈不会存在碎片问题,因为栈是先进后出的队列,内存块弹出栈之前,在其上面的后进的栈内容已弹出。而频繁申请释放操作会造成堆内存空间的不连续,从而造成大量碎片,使程序效率降低。
可见,堆容易造成内存碎片;由于没有专门的系统支持,效率很低;由于可能引发用户态和内核态切换,内存申请的代价更为昂贵。所以栈在程序中应用最广泛,函数调用也利用栈来完成,调用过程中的参数、返回地址、栈基指针和局部变量等都采用栈的方式存放。所以,建议尽量使用栈,仅在分配大量或大块内存空间时使用堆。
- 最后使用栈和堆时应避免越界发生,否则可能程序崩溃或破坏程序堆、栈结构,产生意想不到的后果。
4.动态内存管理和结构体
- 结构体变量和内置类型都有局部,全局,动态生存期。
- 结构体也可以嵌套指向自身的指针。
我们可以一个个的动态申请struct Student类型的内存空间,然后用next指针把它们链接起来。