要想弄懂这个问题,我们得稍微的理解一下计算机指令相关的知识。
1,2,3,4,5,6,7.我直接写出这七个数字。你会一脸懵逼。因为对于这些数字我们没有给他们赋予任何特殊的含义。接下来我们给这七个数字赋予一定的含义。
1:睡觉
2:吃饭
3:洗澡
4:散步
5:打游戏
6:学习
7:去超市
这个时候我们就拥有了一个迷你的指令集。{1,2,3,4,5,6,7}
这个时候当你看到一串指令的时候你就能够理解。比如说65231。它有且只有一种意思那就是:先学习,然后打游戏,之后再吃饭,吃完饭就要洗澡,最后睡觉。没有二义性是因为我们的指令集足够小。假设我们有23这样的指令,那指令就出现二义性了这在计算机中是不允许的,因为计算机会无法决定是6,5,2,3,1还是6,5,23,1 。
但是计算机无法理解10进制,因为它是由各种电路组成的,电路里面某一个地方的状态只有高电平低电平两种状态。因此我们用二进制数来表示指令,使用01串来表示指令串。实际的指令不一定是固定长度的,有会在指令里面有一些数字(立即数)。所有实际情况会复杂得多。
通过这种方式我们给不同的01指令赋予不同的含义并用硬件电路实现。常见的指令包括,把数据从指定内存处加载到对应寄存器,加法操作......等等。感兴趣的读者可以自行了解下。早期的程序员写的是机器码程序。但是这种方式太过繁琐且难懂,可读性极差。比如说指令串01100101001000110001,对于这样一段程序无论是编写还是阅读都是难以接受的(你不可能一眼就看出来它和65231是同一串指令)。但是如果我们用易懂的英文片段来编写则会容易得多。
如果你见到以下代码,learn;game;eat;bathe;sleep;那么你能一眼看出来这串指令的含义,并且极大的易于编写(汇编器的作用就是将汇编语言转换成计算机可以识别的机器码)。因此才有了汇编语言的出现,它和机器码一一对应(伪码除外)。书写汇编代码,需要我们对计算机的硬件系统很熟悉,比如说寄存器,栈,各种设备的端口等。比如说你想读取磁盘的某一块的内容,你就需要给出要读取的扇区编号,要读取多少字节,并且这些参数值要准确的被放在相应的位置,不然磁盘驱动就无法正常读取。总而言之汇编语言的出现极大提升了写程序的效率,但是仍然需要我们直接和硬件打交道,我们需要耗费时间去研究硬件。并且随着程序越来越复制,汇编语言书写的程序可读性也比较差,因为汇编语言的一条指令的操作比较简单,为了实现一个简单的程序,我们往往需要书写大量的代码。
因此我们需要高级语言。高级语言书写的代码就会更加通俗易懂,并且屏蔽了底层硬件细节。程序员可以专注于开发程序了。剩下的交给编译器(编译器的作用在于将高级语言转成汇编语言)。
对于一段c程序,它经历了如下阶段。
预处理器是对c源码的头文件进行处理。编译器负责把hello.i这样的c程序翻译成汇编语言代码hello.s。汇编器再把hello.s翻译成hello.o。由于我们的程序可能引用了别的文件中的变量或者方法,因此需要连接器做链接,最后就形成了一个可以被取值执行的二进制代码。
因此当我们写了一段c程序并且编译完成之后。它会编译成机器码指令存在文件中。当我们需要执行的时候,需要先把该文件的内容加载到内存之后再由计算机取值执行。这样你的c程序对应的机器码指令就会被一条条执行。