数据结构--内存分配中的栈(stack)与堆(heap)

C语言内存分配中的堆和栈
一、在C语言中,内存分配方式有如下三种形式:
1、从静态存储区域分配:
    它是由编译器自动分配和释放的,即内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在,直到整个程序运行结束时才被释放,如全局变量与static类型的变量。
2、在栈上分配:
    它同样也是由编译器自动分配和释放的,即在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元将被自动释放。
3、从堆上分配:
    也被称为动态内存分配,它是由程序员手动完成申请和释放的。即程序在运行的时候由程序员使用内存分配函数(如malloc函数)来申请分配多少的内存,使用完之后再由程序员自己负责使用内存释放函数(如free函数)来释放内存。也就是说,动态内存的整个生存期是由程序员自己决定的,使用非常灵活。需要注意的是,如果在堆上分配了内存空间,就必须及时释放它,否则将会导致运行的程序出现内存泄漏等错误。

#include <stdio.h>
#include <malloc.h>

/**
 * 2019-1-20 20:34:40
 * platform:codeblocks
 */
int
main(int argc, char *argv[])
{
    int  stack1=1;
    int  stack2=2;
    int  stack3=3;
    int  stack4=4;

    printf("stack:dowm\n");
    printf("stack1 = 0x%p\n", &stack1);
    printf("stack2 = 0x%p\n", &stack2);
    printf("stack3 = 0x%p\n", &stack3);
    printf("stack4 = 0x%p\n", &stack4);

    printf("************************************\n");
    int  *heap1 = (int *)malloc(sizeof(int));
    int  *heap2 = (int *)malloc(sizeof(int));
    int  *heap3 = (int *)malloc(sizeof(int));
    int  *heap4 = (int *)malloc(sizeof(int));

    printf("heap:up\n");
    printf("p1 = 0x%p\n", heap1);
    printf("p2 = 0x%p\n", heap2);
    printf("p3 = 0x%p\n", heap3);
    printf("p4 = 0x%p\n", heap4);

    /*free*/
    free(heap1);
    heap1 = NULL;

    free(heap2);
    heap2 = NULL;

    free(heap3);
    heap3 = NULL;

    free(heap4);
    heap3 = NULL;

    return 0;
}

#if 0
program result:

stack:dowm
stack1 = 0x0028FF0C
stack2 = 0x0028FF08
stack3 = 0x0028FF04
stack4 = 0x0028FF00
************************************
heap:up
p1 = 0x003C0F98
p2 = 0x003C0FB8
p3 = 0x003C0FC8
p4 = 0x003C0FD8

#endif // 0


内存中的栈区主要用于分配局部变量空间,其栈地址是向下增长的;而堆区则主要用于分配程序员申请的内存空间,堆地址是向上增长的。
二、内存分配中的堆与栈的区别:
1、分配和释放的方式:
栈:
    栈内存是由编译器自动分配与释放的,它有两种分配方式:静态分配和动态分配。
        静态分配是由编译器自动完成的,如局部变量的分配(即在一个函数中声明一个 int 类型的变量i时,编译器就会自动开辟一块内存以存放变量 i)。与此同时,其生存周期也只在函数的运行过程中,在运行后就释放,并不可以再次访问。
        动态分配由 alloca 函数进行分配,但是栈的动态分配与堆是不同的,它的动态分配是由编译器进行释放,无需任何手工实现。值得注意的是,虽然用 alloca 函数可以实现栈内存的动态分配,但 alloca 函数的可移植性很差,而且在没有传统堆栈的机器上很难实现。因此,不宜使用于广泛移植的程序中。当然,完全可以使用 C99 中的变长数组来替代 alloca 函数。
堆:
    堆内存则不相同,它完全是由程序员手动申请与释放的,程序在运行的时候由程序员使用内存分配函数(如 malloc 函数)来申请任意多少的内存,使用完再由程序员自己负责使用内存释放函数(如 free 函数)释放内存
        如:
        int *pointer1 = (int *)malloc(sizeof(int));  //分配堆内存
        ------
        free(pointer1);//释放堆内存
        pointer1 = NULL;
    对栈内存的自动释放而言,虽然堆上的数据只要程序员不释放空间就可以一直访问,但是,如果一旦忘记了释放堆内存,那么将会造成内存泄漏,导致程序出现致命的错误。
2、分配的碎片问题:
堆:
    对堆来说,频繁分配和释放(malloc / free)不同大小的堆空间势必会造成内存空间的不连续,从而造成大量碎片,导致程序效率降低;
栈:
    对栈来讲,则不会存在这个问题。
3、分配的效率:
堆:
    堆是机器系统提供的数据结构,计算机会在底层对栈提供支持,例如,分配专门的寄存器存放栈的地址,压栈出栈都有专门的执行指令,这就决定了栈的效率比较高。一般而言,只要栈的剩余空间大于所申请空间,系统就将为程序提供内存,否则将报异常提示栈溢出。
栈:
    堆则不同,它是由 C/C++ 函数库提供的,它的机制也相当复杂。例如,为了分配一块堆内存,首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆节点,然后将该节点从空闲节点链表中删除,并将该节点的空间分配给程序。而对于大多数系统,会在这块内存空间的首地址处记录本次分配的大小,这样,代码中的 delete 语句才能正确释放本内存空间。另外,由于找到的堆节点的大小不一定正好等于申请的大小,系统会自动将多余的那部分重新放入空闲链表中。很显然,堆的分配效率比栈要低得多。
4、申请的大小限制:
堆:
    由于操作系统是用链表来存储空闲内存地址(内存区域不连续)的,同时链表的遍历方向是由低地址向高地址进行的。因此,堆内存的申请大小受限于计算机系统中有效的虚拟内存。
栈:
    而栈则不同,它是一块连续的内存区域,其地址的增长方向是向下进行的,向内存地址减小的方向增长。由此可见,栈顶的地址和栈的最大容量一般都是由系统预先规定好的,如果申请的空间超过栈的剩余空间时,将会提示溢出错误。由此可见,相对于堆,能够从栈中获得的空间相对较小。
    
5、存储的内容:
栈:
    对栈而言,一般用于存放函数的参数与局部变量等。例如,在函数调用时,第一个进栈的是(主函数中的)调用处的下一条指令(即函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数 C 编译器中,参数是由右往左入栈的,最后是函数中的局部变量(注意 static 变量是不入栈的)。
    当本次函数调用结束后,遵循“先进后出”(或者称为“后进先出”)的规则,局部变量先出栈,然后是参数,最后栈顶指针指向最开始保存的地址,也就是主函数中的下一条指令,程序由该点继续运行。下面的示例代码可以清晰反映这种入栈顺序:

#include<stdio.h>

void
f(int i)
{
    printf("%d,%d,%d,%d\n", i, i++, i++, i++);
}
int
main(int argc, char *argv[])
{
    int i = 1;
    f(i);

    return 0;
}

#if 0
program result
4,3,2,1
#endif // 0


    由于栈的“先进后出”规则,所以程序最后的输出结果是“4,3,2,1”。
    对堆而言,具体存储内容由程序员根据需要决定存储数据。
三、C语言中各类型变量的存储位置和作用域。
    全局变量。从静态存储区域分配,其作用域是全局作用域,也就是整个程序的生命周期内都可以使用。与此同时,如果程序是由多个源文件构成的,那么全局变量只要在一个文件中定义,就可以在其他所有的文件中使用,但必须在其他文件中通过使用extern关键字来声明该全局变量。    
    全局静态变量。从静态存储区域分配,其生命周期也是与整个程序同在的,从程序开始到结束一直起作用。但是与全局变量不同的是,全局静态变量作用域只在定义它的一个源文件内,其他源文件不能使用。
    局部变量。从栈上分配,其作用域只是在局部函数内,在定义该变量的函数内,只要出了该函数,该局部变量就不再起作用,该变量的生命周期也只是和该函数同在。
    局部静态变量。从静态存储区域分配,其在第一次初始化后就一直存在直到程序结束,该变量的特点是其作用域只在定义它的函数内可见,出了该函数就不可见了。
    
    
    
    
    
    
    
    
    

猜你喜欢

转载自blog.csdn.net/tyustli/article/details/86566148