zoukankan      html  css  js  c++  java
  • 转:程序内存空间(代码段、数据段、堆栈段)

    https://blog.csdn.net/ywcpig/article/details/52303745

    在冯诺依曼的体系结构中,一个进程必须有:代码段,堆栈段,数据段。

    进程的虚拟地址空间图示如下:

    BSS段:BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。

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

    代码段:代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。

    堆(heap):堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)

    栈(stack):栈又称堆栈, 是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进后出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。

    它是由操作系统分配的,内存的申请与回收都由OS管理。

    注意:

    全局的未初始化变量存在于.bss段中,具体体现为一个占位符;全局的已初始化变量存于.data段中;而函数内的自动变量都在栈上分配空间。.bss是不占用可执行文件空间的,其内容由操作系统初始化(清零);而.data却需要占用,其内容由程序初始化,因此造成了上述情况。

    bss段(未手动初始化的数据)并不给该段的数据分配空间,只是记录数据所需空间的大小。
    data(已手动初始化的数据)段则为数据分配空间,数据保存在目标文件中。 数据段包含经过初始化的全局变量以及它们的值。BSS段的大小从可执行文件中得到 ,然后链接器得到这个大小的内存块,紧跟在数据段后面。当这个内存区进入程序的地址空间后全部清零。包含数据段和BSS段的整个区段此时通常称为数据区。

    堆栈段:

      1. 为函数内部的局部变量提供存储空间。

      2. 进行函数调用时,存储“过程活动记录”。

      3. 用作暂时存储区。如计算一个很长的算术表达式时,可以将部分计算结果压入堆栈。

    数据段(静态存储区):

      包括BSS段(Block Started by Symbol)的数据段。BSS段存储未初始化或初始化为0的全局变量、静态变量,具体体现为一个占位符,并不给该段的数据分配空间,只是记录数据所需空间的大小。数据段存储经过初始化的全局和静态变量。

     

    [cpp] view plain copy
     
    1. #define DEBUG "debug"    
    2.     
    3. int space[1024][1024];    
    4. int data = 1;    
    5. int no_data = 0;    
    6.     
    7. int main()    
    8. {    
    9.   char *a = DEBUG;    
    10.   return 1;    
    11. }    

      使用nm查看后

    [cpp] view plain copy
     
    1. 0000000000600660 d _DYNAMIC    
    2. 00000000006007f8 d _GLOBAL_OFFSET_TABLE_    
    3. 0000000000400578 R _IO_stdin_used    
    4.                  w _Jv_RegisterClasses    
    5. 0000000000600640 d __CTOR_END__    
    6. 0000000000600638 d __CTOR_LIST__    
    7. 0000000000600650 D __DTOR_END__    
    8. 0000000000600648 d __DTOR_LIST__    
    9. 0000000000400630 r __FRAME_END__    
    10. 0000000000600658 d __JCR_END__    
    11. 0000000000600658 d __JCR_LIST__    
    12. 0000000000600820 A __bss_start    
    13. 0000000000600818 D __data_start    
    14. 0000000000400530 t __do_global_ctors_aux    
    15. 00000000004003e0 t __do_global_dtors_aux    
    16. 0000000000400580 R __dso_handle    
    17.                  w __gmon_start__    
    18. 0000000000600634 d __init_array_end    
    19. 0000000000600634 d __init_array_start    
    20. 0000000000400490 T __libc_csu_fini    
    21. 00000000004004a0 T __libc_csu_init    
    22.                  U __libc_start_main@@GLIBC_2.2.5    
    23. 0000000000600820 A _edata    
    24. 0000000000a00840 A _end    
    25. 0000000000400568 T _fini    
    26. 0000000000400358 T _init    
    27. 0000000000400390 T _start    
    28. 00000000004003bc t call_gmon_start    
    29. 0000000000600820 b completed.6347    
    30. 000000000060081c D data    
    31. 0000000000600818 W data_start    
    32. 0000000000600828 b dtor_idx.6349    
    33. 0000000000400450 t frame_dummy    
    34. 0000000000400474 T main    
    35. 0000000000600830 B no_data    
    36. 0000000000600840 B space  

      可以看到变量data被分配在data段,而被初始化为0的no_data被分配在了BSS段。

      .bss是不占用.exe文件空间的,其内容由操作系统初始化(清零);而.data却需要占用,其内容由程序初始化。

      注意:.data和.bss在加载时合并到一个Segment(Data Segment)中,这个Segment是可读可写的。

    代码段:

      又称为文本段。存储可执行文件的指令;也有可能包含一些只读的常数变量,例如字符串常量等。

      .rodata段:存放只读数据,比如printf语句中的格式字符串和开关语句的跳转表。也就是你所说的常量区。例如,全局作用域中的 const int ival = 10,ival存放在.rodata段;再如,函数局部作用域中的printf("Hello world %d ", c);语句中的格式字符串"Hello world %d ",也存放在.rodata段。

      但是注意并不是所有的常量都是放在常量数据段的,其特殊情况如下:

      1)有些立即数与指令编译在一起直接放在代码段。

    [cpp] view plain copy
     
    1. int main()    
    2. {    
    3.   int a = 10;    
    4.   return 1;    
    5. }    


      a是常量,但是它没有被放入常量区,而是在指令中直接通过立即数赋值

      2)对于字符串常量,编译器会去掉重复的常量,让程序的每个字符串常量只有一份。

    [cpp] view plain copy
     
    1. char *str = "123456789";    
    2. char *str1 = "helloworld";    
    3.     
    4. int main()    
    5. {    
    6.   char* a = "helloworld";    
    7.   char b[10] = "helloworld";    
    8.   return 1;    
    9. }    

      汇编代码如下:

     

    [cpp] view plain copy
     
    1.                 .file   "hello.c"    
    2. .globl str    
    3.         .section        .rodata    
    4. .LC0:    
    5.         .string "123456789"    
    6.         .data    
    7.         .align 8    
    8.         .type   str, @object    
    9.         .size   str, 8    
    10. str:    
    11.         .quad   .LC0    
    12. .globl str1    
    13.         .section        .rodata    
    14. .LC1:    
    15.         .string "helloworld"    
    16.         .data    
    17.         .align 8    
    18.         .type   str1, @object    
    19.         .size   str1, 8    
    20. str1:    
    21.         .quad   .LC1    
    22.         .text    
    23. .globl main    
    24.         .type   main, @function    
    25. main:    
    26. .LFB0:    
    27.         .cfi_startproc    
    28.         pushq   %rbp    
    29.         .cfi_def_cfa_offset 16    
    30.         .cfi_offset 6, -16    
    31.         movq    %rsp, %rbp    
    32.         .cfi_def_cfa_register 6    
    33.         movq    $.LC1, -8(%rbp)    
    34.         movl    $1819043176, -32(%rbp)    
    35.         movl    $1919907695, -28(%rbp)    
    36.         movw    $25708, -24(%rbp)    
    37.         movl    $1, %eax    
    38.         leave    
    39.         .cfi_def_cfa 7, 8    
    40.         ret    
    41.         .cfi_endproc    
    42. .LFE0:    
    43.         .size   main, .-main    
    44.         .ident  "GCC: (GNU) 4.4.6 20110731 (Red Hat 4.4.6-3)"    
    45.         .section        .note.GNU-stack,"",@progbits    

      可以看到str1和a同时指向.rodata段中同一个LC1

      3)用数组初始化的字符串常量是没有放入常量区的。

      4)用const修饰的全局变量是放入常量区的,但是使用const修饰的局部变量只是设置为只读起到防止修改的效果,没有放入常量区。
      5)有些系统中rodata段是多个进程共享的,目的是为了提高空间的利用率。

      注意:程序加载运行时,.rodata段和.text段通常合并到一个Segment(Text Segment)中,操作系统将这个Segment的页面只读保护起来,防止意外的改写。

    堆:

      就像堆栈段能够根据需要自动增长一样,数据段也有一个对象,用于完成这项工作,这就是堆。堆区域是用来动态分配的内存空间,用 malloc 函数申请的,用free函数释放。calloc、realloc和malloc类似:前者返回指针的之前把分配好的内存内容都清空为零;后者改变一个指针所指向的内存块的大小,可以扩大和缩小,它经常把内存拷贝到别的地方然后将新地址返回。

     

    栈、堆辨析:

    1、栈区(stack):由编译器自动分配释放 ,存放函数的参数值、局部变量的值等。其操作方式类似于数据结构中的栈。 
    2、堆区(heap):由程序员分配释放, 若程序员不释放,程序结束时可能由操作系统回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。 

     

    程序示例:

    1、举个例子说明各种变量存放在什么区: 

    [cpp] view plain copy
     
    1. #include <stdlib.h>  
    2.   
    3. int a=123; //a在全局已初始化数据区   
    4.   
    5. char *p1; //p1在BSS区(未初始化全局变量)   
    6.   
    7. int main()  
    8. {  
    9.     int b; //b为局部变量,在栈区   
    10.   
    11.     char s[]="abc"//s为局部数组变量,在栈区   
    12.             //"abc"为字符串常量,存储在已初始化数据区   
    13.   
    14.     char *p1,*p2; //p1,p2为局部变量,在栈区   
    15.   
    16.     char *p3="123456"//p3在栈区,"123456"在常量区(.rodata)  
    17.   
    18.     static int c=456; //c为局部(静态)数据,在已初始化数据区   
    19.   
    20.     //静态局部变量会自动初始化(因为BSS区自动用0或NULL初始化)  
    21.   
    22.     p1=(char*)malloc(10); //分配得来的10个字节的区域在堆区   
    23.   
    24.     p2=(char*)malloc(20); //分配得来的20个字节的区域在堆区   
    25.   
    26.     free(p1);  
    27.   
    28.     free(p2);  
    29.   
    30.     p1=NULL; //显示地将p1置为NULL,避免以后错误地使用p1  
    31.   
    32.     p2=NULL;  
    33. }  


    2、我们再写一个程序,输出各变量的内存空间:

    [cpp] view plain copy
     
    1. #include <stdio.h>  
    2. #include <stdlib.h>  
    3.   
    4. extern void afunc(void);  
    5.   
    6. extern etext,edata,end;  
    7.   
    8. int bss_var;//未初始化全局变量存储在BSS段  
    9.   
    10. int data_var=42;//初始化全局存储在数据段  
    11.   
    12. #define SHW_ADR(ID,I) printf("The %s is at address: %8x ",ID,&I);//打印地址宏  
    13.   
    14. int main(int argc,char *argv[])  
    15. {  
    16.     char *p,*b,*nb;  
    17.   
    18.     printf("etext address: %8x edata address: %8x end address: %8x ",&etext,&edata,&end);  
    19.   
    20.     SHW_ADR("main",main);//查看代码段main函数位置  
    21.   
    22.     SHW_ADR("afunc",afunc);//查看代码段afunc函数位置  
    23.   
    24.     printf(" bss Locatoin: ");  
    25.     SHW_ADR("bss_var",bss_var);//查看BSS段变量地址  
    26.   
    27.     printf(" data Location: ");  
    28.     SHW_ADR("data_var",data_var);//查看数据段变量地址  
    29.   
    30.     printf(" Stack Loation: ");  
    31.     afunc();  
    32.     printf(" ");  
    33.   
    34.     p=(char*)alloca(32);//从栈中分配空间  
    35.   
    36.     if(p!=NULL)  
    37.     {  
    38.         SHW_ADR("string p in stack start",*p);  
    39.         SHW_ADR("string p in stack end",*(p+32*sizeof(char)));  
    40.     }  
    41.   
    42.     b=(char*)malloc(32*sizeof(char));//从堆中分配空间  
    43.     nb=(char*)malloc(16*sizeof(char));//从堆中分配空间  
    44.   
    45.     printf(" Heap Location: ");  
    46.     SHW_ADR("allocated heap start",*b);//已分配的堆空间的起始地址  
    47.     SHW_ADR("allocated heap end",*(nb+16*sizeof(char)));//已分配的堆空间的结束地址  
    48.   
    49.     printf(" p,b and nb in stack ");  
    50.     SHW_ADR("p",p);//显示栈中数据p的地址  
    51.     SHW_ADR("b",b);//显示栈中数据b的地址  
    52.     SHW_ADR("nb",nb);//显示栈中数据nb的地址  
    53.   
    54.     free(b);//释放申请的空间,以避免内存泄露  
    55.     free(nb);  
    56. }  
    57.   
    58. void afunc(void)  
    59. {  
    60.     static int level=0;//初始化为0的静态数据存储在BSS段中  
    61.   
    62.     int stack_var;//局部变量,存储在栈区  
    63.   
    64.     if(++level==5)  
    65.         return;  
    66.   
    67.     SHW_ADR("stack_var in stack section",stack_var);  
    68.     SHW_ADR("leval in bss section",level);  
    69.   
    70.     afunc();  
    71. }  
    72.   
    73. /* Output 
    74. etext address:  80488bf edata address:  8049b48 end address:  8049b58    
    75. The main is at address:  80485be 
    76. The afunc is at address:  8048550 
    77.  
    78. bss Locatoin: 
    79. The bss_var is at address:  8049b54 
    80.  
    81. data Location: 
    82. The data_var is at address:  8049b40 
    83.  
    84. Stack Loation: 
    85. The stack_var in stack section is at address: ff9cdf80 
    86. The level in bss section is at address:  8049b50 
    87. The stack_var in stack section is at address: ff9cdf50 
    88. The level in bss section is at address:  8049b50 
    89. The stack_var in stack section is at address: ff9cdf20 
    90. The level in bss section is at address:  8049b50 
    91. The stack_var in stack section is at address: ff9cdef0 
    92. The level in bss section is at address:  8049b50 
    93.  
    94. The string p in stack start is at address: ff9cdf70 
    95. The string p in stack end is at address: ff9cdf90 
    96.  
    97. Heap Location: 
    98. The allocated heap start is at address:  9020078 
    99. The allocated heap end is at address:  90200c8 
    100.  
    101. p,b and nb in stack 
    102. The p is at address: ff9cdfac 
    103. The b is at address: ff9cdfa8 
    104. The nb is at address: ff9cdfa4 
    105. */  


    内存管理函数:
    这里插入一段对void*的解释:
    void*这不叫空指针,这叫无确切类型指针.这个指针指向一块内存,却没有告诉程序该用何种方式来解释这片内存.所以这种类型的指针不能直接进行取内容的操作.必须先转成别的类型的指针才可以把内容解释出来.

    还有'',这也不是空指针所指的内容.''是表示一个字符串的结尾而已,并不是NULL的意思.

    真正的空指针是说,这个指针没有指向一块有意义的内存,比如说:
    char* k;
    这里这个k就叫空指针.我们并未让它指向任意内存.
    又或者
    char* k = NULL;
    这里这个k也叫空指针,因为它指向NULL也就是0,注意是整数0,不是''.

    一个空指针我们也无法对它进行取内容操作.
    空指针只有在真正指向了一块有意义的内存后,我们才能对它取内容.也就是说要这样
    k = "hello world!";
    这时k就不是空指针了.

    void *malloc(size_t size)
    (typedef unsigned int size_t;)
    malloc在内存的动态存储区中分配一个长度为size字节的连续空间,其参数是无符号整型,返回一个指向所分配的连续空间的起始地址的指针。分配空间不成功(如内存不足)时返回一个NULL指针。

    void free(void *ptr)
    free释放掉内存空间。

    void *realloc(void *ptr,size_tsize)
    当需要扩大一块内存空间时,realloc试图直接从堆的当前内存段后面获得更多的内在空间,并返回原指针;如果空间不够就使用第一个能够满足这个要求的内存块,并将当前数据复制到新位置,释放原来的数据块;如果申请空间失败,返回NULL。

    void *calloc(size_t nmemb, size_t size)
    calloc是malloc的简单包装,它把动态分配的内存空间进行初始化,全部清0。此函数的实现描述:
    void *calloc(size_t nmemb, size_t size)
    {
        void *p;

        size_t total;
        total=nmemb*size;

        p=malloc(total);
        if(p!=NULL)//申请空间

            memset(p,'',total);//初始化

     

        return p;
    }

    void *alloca(size_t size);
    alloca在栈中分配size个内存空间(函数返回时自动释放掉空间,无需程序员手动释放),并将空间初始化为0。

     

    Reference:

    https://en.wikipedia.org/wiki/Data_segment

  • 相关阅读:
    IntelliJ IDEA 修改内存大小,使得idea运行更流畅。(转发)
    QueryRunner使用总结
    C#中static修饰符的作用
    Design Support库中的控件
    关于tomcat部署项目的问题
    让后台服务一直运行
    活动和服务的通信
    服务的用法
    数组右移问题
    素数对猜想
  • 原文地址:https://www.cnblogs.com/dingbj/p/segment.html
Copyright © 2011-2022 走看看