机器运行的是本地代码(Native Code)
用某种编程语言编写出来的程序是源代码,保存源代码的文件是源文件。源文件只是文本文件,并不能直接运行,因为CPU只能运行本地代码(机器语言代码)。因此,源代码必须被转换为本地代码。
本地代码里有什么?
Window中.EXE文件的程序内容,就是本地代码。只有机器能够理解,人不能理解。
将它DUMP一下,即每字节以2位16进制(2进制和16进制的转换规则中,二进制中每4位可以转换为十六进制中1位)的形式表现,就可以发现,本地代码其实是数值的集合。每一个数值都代表了一个命令或一个数据。
从源文件到可执行文件的过程
第一步:转换源代码:编译器
编译器:将编程语言编写的源代码转换为本地代码的程序。
编译完成的程序为 .obj 目标文件 ,内容是本地代码。但是还不能够运行。转换每种高级编程语言都需要其专有的编译器,例如C语言的C编译器。
同种编程语言在不同CPU下的编译器也不同。
编译器也是程序,因此也需要适合它的运行环境。
因此,确定一个编译器的种类,需要确定哪种编程语言+哪种CPU+哪种运行环境。而实际上购买和下载时,通常指需要确定编译器产品名称+版本号即可。
第二步:生成.EXE文件与启动:链接器
经过编译器的编译,源文件已经被转换成了.obj 目标文件。
链接:将多个目标文件结合起来,生成一个.EXE 可执行文件。
链接器:运行链接的程序。
库文件和标准函数
库文件,即.lib文件,是指将多个目标文件集成保存在一个文件里的形式。
标准函数:在库文件中收录的函数。在程序中,不通过源代码另行编写,而通过库文件提供的函数。
如果主程序中使用了标准函数,在运行链接程序时,链接器就需要指定收录它的库文件,将库文件中需要的.obj目标文件(包含此标准函数)抽取出来,与其他目标文件共同生成一个.EXE可执行文件。
启动
在链接时,必须链接一个特殊的目标文件,它记述了同所有程序起始位置相结合的处理内容,成为程序的启动。
DLL文件和导入库
静态链接库:
静态链接库中包含了目标文件的实体,包含了主程序中使用的标准函数的具体代码,在链接时,直接与目标文件结合。
导入库(.lib)
导入库文件中,不包含目标文件的实体,而只包含了:1.被调用的标准函数位于哪个DLL文件中;2.这个DLL文件所在的文件夹信息。
在链接时,只提供此标准函数的被调用信息参与链接。
动态链接库(.dll)
动态链接库中包含了目标文件的实体,在程序运行时,动态地与.EXE可执行文件结合。
总结
.EXE文件的运行机制
EXE作为单独的文件被储存在磁盘中,被双击打开时,被加载到内存中,由CPU运行。
EXE程序中的变量和函数,是怎样确定在内存中的地址的?
EXE文件给变量和函数分配了虚拟的内存地址。链接时,链接器在EXE文件的开头追加了转换内存地址的必要信息。程序运行时,根据这个信息(称为再配置信息),将虚拟内存地址转换为真实内存地址。
方式:EXE程序给变量和函数分配的虚拟内存地址,作为真实内存地址的基点存在。链接器追加的再配置信息,作为相对地址存在,即相对基点地址的偏移量。
真实内存地址=基点地址+相对地址。
堆和栈
EXE文件的内容分为再配置信息、函数组合变量组。
加载到内存后,还会再生成两个组:堆和栈。
栈:储存函数参数和局部变量的内存区域。
堆:储存程序运行时任意对象和数据的内存区域。
EXE文件中不存在堆和栈,堆和栈是向内存申请的空间。
因此,内存中的文件的构成为:用于变量的内存空间+用于函数的内存空间+用于堆的内存空间+用于栈的内存空间。
内存泄漏
栈:对数据进行存储和清理的代码,由编译器自动生成。
堆:对堆的空间的申请分配和释放,由程序员自己编写代码实现。C语言中,申请分配:malloc()函数,释放:free()函数;C++语言中,申请分配:new运算符,释放:delete运算符。
如果没有明确释放申请分配的内存,就可能造成内存泄漏(memory leak)。