考虑下图显示的 RAM,其中存储了一个非常简单的程序,分为指令块和数据块。该示例与 Charles Petzold 所著的《Code》一书中的示例非常相似:
如您所见,有一个指令块和一个数据块。在书中,这个 RAM 被放置在一台基本的计算机中,您必须使用一些开关(就像旧的 Altair 8800 一样)手动输入数据和指令。为了让机器开始执行指令,你必须设置指令块的起始地址,然后机器开始依次执行一条指令。基本上这个程序所做的就是将值1加载到累加器中,然后添加5,将结果存储在地址000Ch(h代表十六进制)中,最后用Halt指令停止执行。
现在,当我尝试将从本书中获得的知识与 C 源代码的编译方式联系起来时,我有点困惑。具体来说,是代码段和数据段之间存在某种分离的阶段。考虑这个简单的源代码:
#include <stdlib.h>
#include <stdio.h>
int test=10;
int main(){
test ++;
return 0;
}
现在,我的想法是编译器应该告诉计算机执行这样的机器指令:
int test=10; // -> STORE [addressX],10
int main(){
test++; // -> LOAD A,[addressX]
// -> INR A
// -> STORE [addressX],A
return 0;
}
根据维基百科的定义,数据段“包含已初始化的静态变量,即具有已定义值且可以修改的全局变量和局部静态变量”。
在我的简单示例中,变量 test 是一个全局变量。
但是,我的想法是,在将变量放入 RAM 的数据段之前,必须调用某种机器指令(例如 STORE)。不然全局变量怎么存到RAM里面呢?
到底发生了什么,我在这里展示的简单源代码是如何真正分为文本段和数据段的?这个例子的文本段到底是什么?那么数据部分呢?
我可以解释 Windows 上的大致工作原理。
首先,书中给出的信息不适用于当今的操作系统。大多数操作系统(如Windows、Linux等)都有一个可执行文件格式,它描述了代码和数据如何存储在文件中、如何映射到RAM、从哪里开始执行代码等。在 Windows 上,该格式称为
Portable Executable
。 PE格式由零个或多个sections
组成,用于存储代码和其他数据。区段包含一些重要的信息,例如操作系统如何在文件中找到该区段的数据、如何将这些数据映射到内存、内存中的这些数据将采用什么样的保护方法等。部分还可以有一个名称,例如 .text
、.data
、.bss
、.idata
、.rdata
,提供有关该部分包含何种数据的线索。
当您在 Windows 上使用 MSVC 编译和链接代码时,您的程序就有了一个可移植的可执行文件。该 PE 文件将包含一个或多个部分。对于您的示例,它可能有一个用于代码的
.text
,一个用于初始化数据的 .data
,以及一个用于从其他模块导入的 .idata
部分。 .text
部分包含已编译的机器代码,.data
部分包含变量test
的值10的数据。当您执行该文件时,操作系统加载程序将尝试加载、解析并将其映射到为其进程创建的内存中。
因此,您不需要 STORE 指令来在 RAM 中存储和初始化数据。程序中的所有数据都位于相应的部分,并将由加载器映射到内存中。