C++抽象编程——内存模式(2)——函数调用机制

内存地址

在典型计算机的存储器系统中,每个字节由数字地址标识。计算机中的第一个字节编号为0,第二个编号为1,依此类推,直到机器中的字节数减去1。例如,一个小型64KB计算机中的存储器地址将以一个编号为0的字节开始,以一个编号为65,535的字节结束。然而,这些数字表示为十进制值,这不是大多数程序员对地址的看法。鉴于地址与硬件的内部结构密切相关,因此,使用前面介绍的十六进制符号,将地址从地址0000开始并以FFFF结尾是更常见的。然而,重要的是要记住,地址是简单的数字,并且基本的数字只是确定这些数字是如何写下来的。
虽然可以使用十进制地址,但我们最后还是使用十六进制符号,原因如下:

  • 地址号通常以十六进制形式编写,C ++调试器和运行时环境倾向于以此形式显示地址。
  • 以十六进制形式编写地址号码可以更容易地识别特定的数字代表地址而不是一些不明确的整数。举个例子,如果你看到数字65,536,你可以假设它代表一个整数。如果你看到数字FFFF,那么你可以确信该数字代表一个地址。
  • 使用十六进制可以更容易地看出为什么要选择特定的限制。如果将其写为十进制值,则数字65,535似乎是一个相当随机的值。如果以十六进制表示相同的数字为FFFF,则变得更容易识别该值是可以用16位表示的最大值。

虽然内存中的地址通常以字节为单位,但大部分计算机都支持对较大单位(如word)进行操作。在典型的机器中,一个字由四个字节组成,因此可以将四个字节组合在一起来指代单个字。然而,在这种情况下,连续字的地址增加了4。 字节和字寻址之间的区别如图:

图的右侧提供了一个关于在典型的C++程序中如何组织内存的粗略草图。程序中的指令(内部表示为存储在存储单元中的位模式)和全局变量趋向于存储在地址空间开头附近的低编号地址中。在该区域中分配的内存量通常不随程序运行而改变。
内存中最高的地址用于构建栈。每次程序调用函数或方法时,计算机将在此内存区域中创建一个新的栈。当该函数返回时,这个栈被丢弃,留下可用于随后调用的堆栈帧的存储器。这些栈的结构将在以后中进行更详细的描述。
程序数据结束和栈之间的内存区域可供程序使用,以便在运行时需要获取更多内存。该技术在抽象数据类型的设计和实现中是非常重要的,我们在讲完指针后会提到。

将内存值分配给变量

当我们在C ++程序中声明变量时,编译器必须确保该变量被分配足够的内存来保存该类型的值。使用该内存的源取决于变量的声明。全局变量,通常被分配在与程序相同的存储器区域中,在大多数架构中,这些变量现在在内存中的相对较小的地址上出现。 因此,如果编译器看到声明:

const double PI = 3.14159

它将在存储器的低地址区域中的某处保留八个字节的存储器,并将该值的3.14159存储在该变量中。作为程序员,我们不知道编译器将选择什么内存地址,但是如果假设在上图中使用该地址,它通常可以帮助你可视化机器内部发生的情况。 这里,例如,您可能会想到,常量PI存储在地址0200中,如下图所示:

然而,大多数变量是局部变量。局部变量在被称为栈的连续地址块中的存储器的高端的堆栈区域中分配。我们其实已经看到从我们提到变量的时候的栈框架,但这些框架已被抽象地表示为框。在内部,这些变量在每个函数调用时被推送到栈顶部的块中被分配空间。
为了具体讨论这个问题,我们写一个简单的PowersOfTwo程序来说明,代码如下:

#include <iostream>
using namespace std;
int raiseToPower(int n, int k);
int main() {
    int limit;
    cout << "This program lists powers of two." << endl;
    cout << "Enter exponent limit: ";
    cin >> limit;
    for (int i = 0; i <= limit; i++) {
        cout << "2 to the " << i << " = "
             << raiseToPower(2, i) << endl;
    }
    return 0;
}
/*这个程序计算n的k次方*/
int raiseToPower(int n, int k) {
    int result = 1;
    for (int i = 0; i < k; i++) {
        result *= n;
    }
    return result;
}

当运行PowersOfTwo程序时,首先发生的是操作系统生成对主函数的调用。main函数没有参数,但是有两个局部变量,limit和i。因此,栈必须为这些整数变量分配空间,如下所示:

FFF4直接到FFF8是因为一个int型占用4个字节。在该图中,变量limit已分配给地址FFF4,变量i出现在FFF8。这些地址在某种实际意义意义上是任意的,因为没有办法准确地预测编译器将分配哪些地址,所以limit可能会在 i 之前或之后出现。你可以指望的是,这两个变量将分配给分配给该栈的区域。图表底部的灰色矩形表示计算机需要跟踪每个函数调用的附加信息超出局部变量的值。如果没有别的,每个栈需要跟踪应该返回的程序中的位置。该信息的格式取决于机器的架构,并不是了解数据模型所必需的。以后我们的栈包括每个栈框架中的一个灰色矩形,以提醒这些额外的信息存在,并使视觉上更容易看到每个框架的范围。
当程序运行时,每个函数都可以访问自己的栈,并更改局部变量的值。假设用户已经输入8作为limit的值,那么在第一次调用raiseToPower之前的情况就像这样:

调用raiseToPower(2,i)在现有的顶部创建一个新的栈。框架包含参数变量n和k的条目,以及局部变量result 和 i。 参数变量被初始化为参数的值,这意味着栈现在看起来像这样:

当raiseToPower返回时,它的栈被丢弃,在调用之前恢复状态。
此示例非常简单,并且不包括创建更精确的内存图的复杂性。然而,这个例子主要给你足够的感觉如何将变量分配给内存来转换到指针的主题,这在下一个系列中介绍。在下一个系列中中,你将有机会去利用指针了解有关内存分配策略的更多信息。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值