C语言内存管理分析

232 阅读6分钟

一个由C/C++编译的程序占用的内存分为以下几个部分:
(1) 栈区(stack) : 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈,地址由高向低延伸。
(2) 堆区(heap): 一般由程序员分配释放,由程序执行过程中动态申请分配的内存,地址由低向高延伸,与栈区正好相反。若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式类似于链表。
(3) 全局区(静态区static) :全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后有系统释放。
(4) 文字常量区: 常量字符串就是放在这里的。程序结束后由系统释放。
(5) 程序代码区: 存放函数体得二进制代码。
在这里插入图片描述
下面通过一段代码来观察内存分配情况:

int a = 0;                              //全局初始化区
char* p1;                               //全局未初始化区

void main( void )
{
    int b;                              //栈
    char s[] = "ssssss";                //栈
    char* p2;                           //栈
    char* p3 = "p3p3p3";                //"p3p3p3\0" 在常量区  p3在栈上
    char* p4 = "p4p4p4";                //"p4p4p4\0"在常量区  p4在栈上
    static int c = 0;                   //全局(静态)初始化区
    p1 = ( char* )malloc( 10 );         //堆
    p2 = ( char* )malloc( 20 );         //堆
    
    __asm( "sim" );                             
    SysClkInit();
    delay_init( 16 );
    Uart1_Init( 9600 );
    __asm( "rim" );
    while( 1 )
    {
        delay_ms( 500 );
        printf("a :%6d,  %#x\r\n",a,&a);
        printf("p1:%6s,  %#x\r\n",p1,&p1);
        printf("b :%6d,  %#x\r\n",b,&b);
        printf("s :%s,  %#x\r\n",s,&s);
        printf("p2:%6s,  %#x\r\n",p2,&p2);
        printf("p3:%s,  %#x\r\n",p3,&p3);
        printf("p4:%s,  %#x\r\n",p4,&p4);
        printf("c :%6d,  %#x\r\n",a,&c);
        printf("\r\n");
    }
}

运行程序后打印结果如下:
在这里插入图片描述
由运行结果可以看出全局变量 a、p1、静态变量 c 的地址在一起紧挨着,a的地址最小。
其他变量地址依次是 s–>b–>p3–>p4–p2。

进一步解读:
(1)代码区(text segment)。代码区指令根据程序设计流程依次执行,对于顺序指令,则只会执行一次(每个进程),如果反复,则需要使用跳转指令,如果进行递归,则需要借助栈来实现。代码段: 代码段(code segment/text segment )通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。代码区的指令中包括操作码和要操作的对象(或对象地址引用)。如果是立即数(即具体的数值,如5),将直接包含在代码中;如果是局部数据,将在栈区分配空间,然后引用该数据地址;如果是BSS区和数据区,在代码中同样将引用该数据地址。另外,代码段还规划了局部数据所申请的内存空间信息。

(2)全局初始化数据区/静态数据区(Data Segment)。只初始化一次。数据段: 数据段(data segment )通常是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。data段中的静态数据区存放的是程序中已初始化的全局变量、静态变量和常量。

(3)未初始化数据区(BSS)。在运行时改变其值。BSS 段: BSS 段(bss segment )通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS 是英文Block Started by Symbol 的简称。BSS 段属于静态内存分配,即程序一开始就将其清零了。一般在初始化时BSS段部分将会清零。

(4)栈区(stack)。由编译器自动分配释放,存放函数的参数值、局部变量的值等。存放函数的参数值、局部变量的值,以及在进行任务切换时存放当前任务的上下文内容。其操作方式类似于数据结构中的栈。每当一个函数被调用,该函数返回地址和一些关于调用的信息,比如某些寄存器的内容,被存储到栈区。然后这个被调用的函数再为它的自动变量和临时变量在栈区上分配空间,这就是C实现函数递归调用的方法。每执行一次递归函数调用,一个新的栈框架就会被使用,这样这个新实例栈里的变量就不会和该函数的另一个实例栈里面的变量混淆。栈(stack) :栈又称堆栈, 是用户存放程序临时创建的局部变量,也就是说我们函数括弧"{}"中定义的变量(但不包括static 声明的变量,static 意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进先出特点,所以栈特别方便用来保存/ 恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。

(5)堆区(heap)。用于动态内存分配。堆在内存中位于bss区和栈区之间。一般由程序员分配和释放,若程序员不释放,程序结束时有可能由OS回收。堆(heap): 堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc 等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free 等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)。在将应用程序加载到内存空间执行时,操作系统负责代码段、数据段和BSS段的加载,并将在内存中为这些段分配空间。栈段亦由操作系统分配和管理,而不需要程序员显示地管理;堆段由程序员自己管理,即显式地申请和释放空间。
在这里插入图片描述