语言基础(5):程序内存结构

1、C++进程内存分布

在C/C++程序进程内存分为:代码区、静态区、常量区、堆区、栈区,不同区域存放不同内容:

内存区 存放内容 说明
代码区 二进制代码 CPU可执行的机器指令,且是只读的
静态区 全局和静态变量 包括局部静态变量
常量区 程序在运行的期间不能够被改变的量 例如: 全局const修饰的变量,字符串常量”abcde”, 数组的名字等
堆区 存放malloc()函数申请的内存 忘记free()会造成内存泄露,程序结束由系统回收
new申请的内存可能在这个区域,取决于new的实现
栈区 存放局部变量 函数内的局部变量、形参和函数返回值,超出数据作用域,被系统回收

几点需要注意:

  • 代码区,不仅存放程序二进制代码,还包括程序中使用的立即数,例如程序中直接出现的数值,int a = 10;// 10就是立即数;
  • 静态区(static), 全局变量和静态变量存放区(RW),内存中,初始化的全局和静态变量在一块区域,未初始化的全局变量和静态变量在相邻的另一块区域(ZI:ZeroInit);
  • 常量区,常变量和字符串常量、变量名等(RO:ReadOnly);

程序需要在计算机硬件上运行起来,其实质就是静态可执行的文件加载到内存中的过程,可执行程序文件只是一个程序的载体,程序运行最终要在内存中进行。

所谓动和静就是指内存分配方式,程序被加载到内存中:

  • 静态分配内存:是在程序编译和链接时就确定好的内存;
  • 动态分配内存:是在程序加载、调入、执行的时候分配/回收的内存;
int a = 0;     // 静态区-初始化区
char *p1;    // 静态区-未初始化区(ZI) 
 
void main() 
{ 
    int b;    // 栈区 
    char s[] = "abc";     // "abc"在常量区,s在栈区。 
    char *p2;     // 栈 
    char *p3 = "123456";     // "123456\0", 在常量区,p3在栈区。 
    static int c =0;    // 全局(静态)初始化区
    
   /*   p1,p2存放在栈区,存放 malloc 内存的首地址
   *    分配得来得10和20字节的区域就在堆区。      */
    p1 = (char *)malloc(10); 
    p2 = (char *)malloc(20); 
    
    strcpy(p1, "123456");     // "123456\0" 放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。 
}

#2、可执行文件结构 可执行文件,以windows为例,后缀格式为 .exe (文件结构是PE格式,linux可执行文件没有格式后缀,文件是ELF格式)文件,它可以加载到内存中,并由操作系统加载程序执行,是可在操作系统存储空间中浮动定位的可执行程序。
##2.1 可执行文件结构 通过dumpbin(win)、objdump(linux)可以查看可执行文件结构: - .text: 也称为代码段(Code),用来存放程序执行代码,同时也可能会包含一些常量(如一些字符串常量、立即数等),该段内存为静态分配,读写方式 RO(某些架构可能允许修改)。 这块内存是共享的,当有多个相同进程(Process)存在时,共用同一个 .text 段。 - .data: 也有的地方叫GVAR(global value),用来存放程序中已经初始化的非零全局变量。静态分配。.data又可分为读写(RW)区域和只读(RO)区域。  - RW段 则是普通非常全局变量,也包括静态变量; - RO段 保存常量所以也被称为 .constdata (linux中是独立的段 .rodata); - .bss 存放程序中为初始化的和零值全局变量,文件中并不分配存储空间,只记录变量空间大小。静态分配,在程序加载时通常会被清零(所以也叫ZI),读写方式 RW。 - stack: 栈,存放Automatic Variables,按内存地址由高到低方向生长,其最大大小由编译时确定,速度快,但自由性差,最大空间不大,很明显该区域是 RW。 - heap: 堆,自由申请的空间,按内存地址由低到高方向生长,其大小由系统内存/虚拟内存上限决定,速度较慢,但自由性大,可用空间大。(每个线程都会有自己的栈,但是堆空间是共用的),该区域RW。

进程空间结构内存映射结构

2.2 可执行文件和进程对应关系

一个程序需要运行起来,除了映射加载到内存中,还需要更多的空间,完成数据间的交互,否则这个程序就是死的。所以我们还需要为更多的数据和数据交互提供一块内存 —— 堆栈。

  • 栈(stack)
    该段对应进程栈区,用于编译期间就能确定存储大小的存储区,主要用于在函数作用域内创建,离开作用域后自动销毁的变量。也即用于局部变量、函数参数等。栈的存储空间是连续的,两个紧挨着定义的局部变量,他们的存储空间也是紧挨着的。栈是向低地址扩展的数据结构,工作方式和数据结构中的栈类似。栈的大小是有限的,VC++编译器的默认栈的大小为1MB,所以不要定义int a[1000000]这样的超大数组,否则就会发生栈溢出(stack overflow)问题。

  • 堆(heap)
    该段进程堆区,用于编译期间不能确定存储大小的变量的存储区,运行期间确定,它的存储空间是不连续的,一般由malloc(可能 new)函数来分配内存块,并且需要用free(delete)函数释放内存。如果没有释放,就会出现内存泄漏(memory leak)问题。堆是向高地址扩展的数据结构,工作方式和数据结构中的链表类似,所以会产生内存碎片。堆的大小受OS虚拟内存大小限制,理论上每个程序最大可达4GB。

  • .data
    该段对应进程分配存储的变量和常量

    • .data(RW)
      映射进程全局/静态区,用于编译期间就能确定存储大小的变量的存储区,但它用于在整个程序运行期间都可见的全局变量和静态变量(全局的、局部的)。
    • .data(RO)/.constdata
      映射进程常量区,和“全局/静态存储区”一样,用于编译期间就能确定存储大小的常量的存储区,并且在程序运行期间,存储区内的常量是全局可见的,这是一块比较特殊的存储去,他们里面存放的是常量,不允许被修改。
    • .bss
      该段同样映射进程全局/静态区,主要对应未初始化全局变量,紧邻.data(RW)内存空间,在编译期间就能确定存储大小,但是可执行文件中不分配存储,程序加载时进程分配内存;

注意:

  1. 堆向高内存地址生长;
  2. 栈向低内存地址生长;
  3. 堆和栈相向而生,堆和栈之间有个临界点,称为stkbrk。

3、程序进程映射示例

3.1 进程内存映射实例

假设有如下一段程序

int main(...) {
    fun_1(...);
    fun_2(...);
    fun_3(...);
    return 0;
}

那么对应的内存映射结构如下所示:
代码内存映射

3.2 函数帧栈

一个函数被调用时所建立的栈帧包含下面的信息:

  • 被调函数的返回地址。返回地址是存放在被调用函数的栈帧里,还是主调函数的栈帧,取决于不同系统的实现;
  • 主调函数的栈帧信息, 即栈顶和栈底;
  • 为被调用函数的参数分配的空间(win 由右至左入栈,取决于不同系统的实现);
  • 为函数的局部变量分配的栈空间。

3.3 linux 对象文件结构实例(和win类似)


#reference [C/C++程序内存的各种变量存储区域和各个区域详解](https://blog.csdn.net/jirryzhang/article/details/79518408) [操作系统执行可执行程序时,内存分配是怎样的](http://nvzhuang.ping-jia.net/jieshao-758155935643838964.htm) [详细的可执行程序结构参考](https://blog.csdn.net/edonlii/article/details/8779075) [C/C++内存分配](https://www.cnblogs.com/yongdaimi/p/6495182.html) [heap 和 free store区别](https://www.cnblogs.com/QG-whz/p/5060894.html)
posted @ 2017-11-23 22:49  ningKing  阅读(1202)  评论(0编辑  收藏  举报