程序运行时的存储结构

程序运行时的存储结构

目标程序在目标机器环境下运行时,只是在自己的运行时的存储空间内完成执行。通常,在有操作系统的情况下,程序在自己的逻辑存储空间内存储和运行。因此,编译程序在生成目标代码时应该明确程序的所有对象在逻辑存储空间是如何存储的,以及目标代码运行时是如何使用和分配自己的逻辑存储空间的。

–摘自《编译原理》第三版 王生原等编著。

:本文中例子以c语言为例。

运行时存储组织

运行时存储组织的作用和任务

编译程序所产生的目标程序本身的大小通常是固定的,一般情况下放在代码区

目标程序所需要的数据对象放在数据区。这里的数据对象既包括用户定义的各种变量或者常量(比如:int a; 其中的a就是一个数据对象),也包括调用过程中所需的连接信息(比如:printf("hello world");,代码区也保存你调用的printf的函数的相关信息)。

数据对象的表示

明确源语言中各类数据对象在目标机器中的表示形式,其实就是确定具体的存储方案。

数据对象在目标机器中通常是以字节(Byte)为单位进行分配,同一类型的数据对象,在不同类型的目标机器字节数可能不同。

对于基本数据对象,比如说integer 通常占4个字节,character占一个字节,而指针类型的数据对象占一个单位字长(单位字长由目标机器位数决定)。

表达式计算

如何正确有效的组织表达式的计算,其中涉及到代码优化等问题。

存储分配策略

采取静态分配还是动态分配。静态分配(比如:c中打的static)是指编译时就确定数据对象的大小,个数。动态分配(比如c中的malloc或者c++和java中的new)是指运行时才能确定对象的存储分配。

过程实现

如何实现函数调用,参数传递,返回值等问题。

程序运行时存储空间的布局

粗略划分可以把程序的存储布局分为代码区和数据区,但是为了存储组织和管理,往往需要将它们划分为更加具体的区域。具体的划分因目标机器而定,但是一般来说,可以划分为以下几个部分:

  • 保留地址区。为操作系统保留的地址区域等。通常是不允许普通程序的读写,只允许操作系统的特权操作读写。
  • 代码区。静态存放编译产生的目标代码。
  • 静态数据区。静态存放全局数据。比如说用的全局变量以及静态变量等。
  • 共享库和分别编译模块。
  • 动态数据区。指的是动态变化的堆区和栈区。堆(数据对象由低地址–>高地址存储)。栈(数据对象由高地址–>低地址存储)。

这里写图片描述

栈式存储分配

采用来存储和管理。

在函数/过程的实现中,每次调用函数/过程时,就在当前栈顶加一个活动记录(activity record)。当函数执行完毕后,释放掉当前函数的栈顶记录。

这也是为什么会有变量的域概念这一说法,函数的局部变量,只在当前函数域内有效,执行完当前函数,就会释放掉当前函数域。

#include <stdio.h>
//计算两个数的加法
int sum(int a, int b)
{
    int c;
    c = a+b;

    return a+b;
}
int main(void)
{
    int a;
    int b;
    int c;
    c = sum(a,b);
    //打印计算结果
    printf("%d+%d = %d",a,b,c);

    return 1;
}

当执行上面的程序时:

  1. 程序从main函数开始执行
  2. 系统为main函数分配栈活动记录,将main函数的活动记录压进栈(push stack),当前栈顶指针指向main函数函数记录。
  3. 当函数执行到15行时,将开始调用sum函数
  4. 系统为sum函数分配栈活动记录,将sum函数的活动记录压进栈(push stack),当前栈顶指针指向sum函数活动记录。
  5. 执行完sum函数,将当前栈顶记录弹出(pop stack),当前栈顶指针指向main函数的活动记录。
  6. 将sum函数的返回值赋值给c。
  7. 函数数执行到17行时,调用系统I/O函数printf。
  8. 系统为printf函数分配栈活动记录,将printf函数的活动记录压进栈(push stack),当前栈顶指针指向printf函数的活动记录。
  9. 执行完printf函数,将当前栈顶记录弹出(pop stack),当前栈顶指针指向main函数的函数记录。
  10. 系统将main函数的活动记录弹出,程序执行完毕。
什么情况下才能该用栈式存储?

在编译时期,过程/函数以及嵌套程序块的活动记录大小(最大值)是可以确定的。

堆式存储分配

从栈式存储中,我们知道,每次函数执行完毕后,都会将当前栈顶活动记录弹出,这意味着变量随着函数执行的结束而丢失。但是有时候我们想要函数结束完之后,当前数据对象长期存在,这时候就该堆发挥作用了。

在堆式存储分配时,程序可以在任意时刻,以任意次序从程序的代码区的堆区申请和释放一个数据对象。通常情况下,堆的分配和释放是由程序向操作系统提出的,这也注定了它的分配效率没有栈那么快。

堆的分配和释放有两种方式:隐式(implicit)和显式(explicit)。

在c语言中没有显式的分配和释放堆的语句,但是可以用标准库中的malloc函数和free函数实现,这需要程序员的水平比较高。在java语言中,堆的释放都是有Java的垃圾回收器来隐式释放的而且这个垃圾回收器相当’聪明‘这也降低了java的入门门槛。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值