C/C++运行时内存环境

在学习C/C++的过程中,熟悉程序在内存的布局环境是很有必要的,也体现着一个合格C/C++程序员必备的素质.例如运行时申请的内存在哪了?共享内存在进程哪块内存中?全局变量为何在所有函数都要效等等...

<!--more-->

C程序内存布局

C程序在内存中由栈,堆,BSS段,数据段,代码段组成,如下图所示:

C程序内存布局

Linux将虚拟内存分为内核态和用户态,内核态为1GB,用户态为3G,每个程序都各自占有自己的3GB内存,不同的进程不能互相访问变量,除非使用Linxu进程间通信机制,有时间把进程间通信机制发上来.

  • 栈:是由系统自动分配和释放的,存储局部变量,由高地址往低地址.例如在某个函数foo中定义的局部变量,int vari,char *p,struct book *b等等,这些变量都存储在栈中,当foo函数结束后,这些变量自动销毁,系统回收内存.

  • 堆:自由存储区.堆区内存是由程序员自己申请和释放的,由低地址向高地址.C语言用函数malloc申请,C++用函数new申请.例如,char *p=new char.那么new char申请的内存在堆中,p存储在栈中,并指向堆中刚申请的内存.

  • 缓冲区:缓冲区是一块很大内存,这样一来,栈和堆是不会相交的.进程共享内存也是存在这块内存中.

  • 数据段和BSS:合称静态区(全局区).存储静态和全局变量.数据段主要是存储已初始化的全局变量,静态变量(全局和局部)和常量数据(字符串常量).BSS区存储为初始化的全局未初始化变量.

  • 代码段:主要是存储程序运行的代码.为只读.

C程序函数栈分配方式

分析完程序的内存布局之后,接下来,分析下c函数栈的分配.示意图如下;

C程序函数栈的分配方式

假如由以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <stdlib.h>
int sum(int a,int b)
{
int sum;
sum=a+b;
return sum;
}
int main(void)
{
int a,b,result=0;
scanf("%d %d",&a,&b);
result=sum(a,b);
printf("%d\n",result);
return 0;
}

则当在main函数中调用sum函数时,sum函数的栈布局如上图所示.

  1. main函数首先将参数按从右到左的顺序(即先b在a)压入栈,接着把返回地址压入栈.
  2. 程序跳转到sum函数中,sum函数将原BP指针压入栈,然后BP新值即为原BP值的地址.BP称为帧指针,通过BP添加偏移量即可取参数返回地址以及局部变量.例如参数2可以通过BP+12获得,返回地址通过BP+4获得等等.
  3. sum函数把局部变量压入局部变量内存区,地址从低地址向高地址.这就解决我之前一个困惑.先前提到栈是从高地址向低地址的,但是我们平时在编程过程中,申请一个数组,然后数组的指针是加1,向高地址走的.原因就在此.
  4. 最后就是通用寄存器.因为被调用的函数可能更改调用函数中存储数据的寄存器,所以要在被调用函数中保存原寄存器的值,然后被调用函数结束之后,在把这些值弹出到寄存器中.

当被调用函数sum函数结束之后,返回值被存储在寄存器EAX中,然后调用函数main函数从寄存器EAX中获取返回值.

至此,C程序运行时内存环境就分析完了.