目录
今天来谈谈,一句代码,是如何从被编辑到被执行的。以下为本人在阅读相关书籍资料后的初步理解,仅供参考,如有错误,还望各位老师不吝指教,多谢!
我要运行一个程序,我首先得写下来吧!比如说我现在在IDE中写下了以下程序:
#include "stdafx.h"
#include "iostream"
int main()
{
int x = 0;
int y;
y = x + 1;
return 0;
}
这个程序应当是非常简单,仅次于“哈喽我的”了,现在,这堆代码就存在于磁盘上了,也就是我们电脑中的ABCDEFG盘等等。这个时候的程序,是以文本形式存放在磁盘上的,什么叫文本形式呢?即是将程序中的每个字符以其ASCII码值进行存储在文件中,借用《CSAPP》书中的“哈喽我的”程序存储形式,如下所示:
这样实际上文件中是按字节序列存储的一系列整数值,而每一个字节的整数值都对应一个字符。
那么当我运行这段程序后,会发生什么呢?
1.预处理
在这个阶段主要实现以下几个功能:
将所有的"#define"删除,并且展开所有的宏定义;
处理所有条件预编译指令,比如"#if"、"#ifdef"、"#elif"、"#else"、"#endif";
处理"#include"预编译指令,将被包含的文件插入到该预处理指令位置。这是一个递归过程,也就是说被包含的文件可能还 包含其他文件;
删除所有的注释;
添加行号和文件名标识,以便编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号;
保留所有的#pragma编译器指令,因为编译器需要使用它们;
预处理后,通常以.i作为文件扩展名,此时的程序依然为文本形式。
2.编译
简单来说,编译过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后生产相应的汇编代码文件。编译后的文件通常以.s作为文件扩展名,此时的程序为汇编语言构成的文本形式。如下所示(仅截取一部分):
图中,红色框标注的就是经过编译后形成的汇编代码,也可以看出这段汇编代码就是在对函数栈帧进行操作的。尽管已经翻译为了汇编语言,但是电脑又不懂英语,它怎么知道“push”、"mov"是什么呢?不用着急,接下来,就进入最最重要的环节,来将汇编代码翻译成电脑看得懂的东西——汇编。
3.汇编
汇编阶段会将第二步中生成的汇编语言翻译为一串二进制序列,这些0和1就是最终和CPU打交道的数字。
汇编是编译器完成的。在分析汇编阶段之前,不得不说一下指令集。我所理解的指令集有两种,一种是汇编指令集,一种是CPU指令集。其中汇编指令集是将汇编代码翻译为一串二进制代码,不同的汇编代码应当对应不同的二进制代码(机器码),而如何将不同的汇编代码翻译成相对应的二进制代码,这就需要根据汇编指令集来翻译了。而对于CPU指令集,当CPU识别到一串二进制代码(机器码)后,CPU会根据CPU指令集来决定自己要干什么,然后相应的操作寄存器、访问内存等等。。。常常提到的“x86、x64、x86-64、IA-32、IA-64"其实也就是CPU指令集了,它们规定了CPU在接收到什么样的机器码后必须做出什么样的行为。而汇编这个阶段,当然需要用到的就是汇编指令集了。
汇编实际上就是一个对汇编代码进行二进制编码成机器码的过程,由于编码出机器码最终是与CPU沟通的,并且我还没能够掌因此,与其根据汇编代码来分析机器码,不如从机器码来进行译码分析。以下以IA-32指令体系为例。
3.1 机器码的格式
机器码的格式如图所示。