编程感受

本文深入浅出地解析了计算机如何理解并执行高级语言程序,通过一个简单示例展示了编译后的机器码形式,揭示了计算机执行指令的本质。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、     计算机懂cc++vc++。。。。。。吗?

语言是交流或者记录思想或者事情的载体。是沟通的工具。那么在计算机的语言世界里,有cc++vc++这样语言吗?答案是没有。在计算机的世界里只有一种语言就是机器语言。那么什么是机器语言呢?要解释这个问题,就得了解一下计算机认识的机器码。

 

二、计算机认识的机器码

根据冯诺依曼体系结构,计算机是由五大部分构成的。即:运算器、控制器、存储器、输入设备、输出设备。而运算器、控制器合在一起统称CPU,即中央处理器。其中存储器中存储着我们的程序和要处理的数据,当然这儿的程序或者说代码指的是计算机能懂的机器码。控制器负责控制整个计算机的运行,在控制器的控制之下,计算机按如下步骤工作:从存储器中取出指令,即代码;然后对指令进行译码;然后执行;最后存储执行后的结果。

那么控制器是如何控制整个计算机的运行的呢?什么是译码呢?译码的结果是什么呢?下面我们以一个简单的加法指令的执行过程。来解释上面的三个问题。

执行一个加法的机器码对应的汇编语言是:ADD  EAXEBX

关于EAXEBX我将在后面详细的讲解CPU的结构的时候再给大家解释。现在你要知道的是EAXEBXCPU内部的两个寄存器。所谓寄存器,简单的说就像存钱罐一样,是用来存储东西的。当然存钱罐是有盖子的,打开盖子你才能将钱存入存钱罐或者从存钱罐中取出钱来。同样的道理,CPU里面有很多的寄存器,8086CPU14个命名寄存器,386及其以上的CUP至少有20个命名寄存器。那么什么是命名寄存器呢?就是我们可以通过程序访问的寄存器,为了访问这些寄存器,汇编语言给它一个名字,比如EAXEBX都是命名寄存器。还有未命名寄存器,比如说AR地址寄存器,DR数据暂存器等。这些寄存器和命名寄存器的区别是,它只能有CPU控制器来访问,而无法用程序来访问。(当然,所有的寄存器,在机器码中都有一个二进制的代号。)正如存钱罐一样,这些寄存器也是有盖子的,并且有两个盖子。其中一个盖子用于将数据存入到寄存器中去,另外一个盖子用于从寄存器中取数据。CPU为每个寄存器的两个盖子分别设置了控制开关,用于控制盖子的打开和关闭。这样我们就得出结论,那就是CPU的每个寄存器都存在两个控制开关,一个控制数据的输入,一个控制数据的输出。然而,这些开关的状态从哪儿来呢?答案是由机器码译码得到。至此,我们就可以回答上面的三个问题了。首先,控制器依靠指令来控制整个计算机的运行;其次,译码是将机器码翻译成计算机中的输入输出开关的状态;最后,译码的结果是:一条机器码译码的结果就是计算机中所有的输入输出控制开关的状态。

回答了上面三个问题,我们现在再来看看ADD  EAXEBX指令的执行。

ADD  EAXEBX指令,是将EBX中的内容和EAX的内容输入到执行单元的加法其中进行加法运算,结果存入到EAX中。为了说明此执行我们可以看看图1-1

 

图1_1 ADD EAX,EBX的执行图

如图1_1可知,ADD EAX,EBX对应的机器码,被译码后,s_eax_out、s_alu1_in、s_ebx_out、s_alu2_in、s_alu_out、s_eax_in开关会合上。也就是译码后这些状态会有效。而其他的状态(CPU所包含的所有开关状态)都会是无效的。

需要说明的是,即使s_eax_out、s_alu1_in、s_ebx_out、s_alu2_in、s_alu_out、s_eax_in是一条机器指令译码的结果,并且都是有效的。但是,他们必须按照一定的按照一定时序工作。也就是,他们并不是在同一时刻有效的。显然我们可以看得出其有效的顺序是:

1、                        s_eax_out、s_alu1_in、s_ebx_out、s_alu2_in同时有效,两个加数输入到ALU算术逻辑单元中去。

2、                        进行加法运算。

3、                        s_eax_out、s_alu1_in、s_ebx_out、s_alu2_in无效,s_alu_out、s_eax_in有效,将运算结果存入到EAX中去。

4、                        s_alu_out、s_eax_in无效,指令执行结束。

可见,这些由机器码译码得到的状态,还会按照一定的时序起作用。我们的计算机有一个叫主频的东西,它是计算机的工作频率。世界的万事万物都有它的工作频率。比如一年有春夏秋冬、一日有黑白交替;人有心率、有呼吸的频率。这都是我们的工作频率。计算机的工作频率就是主频。主频在计算机内部会分解成四种状态,即:T1、T2、T3、T4。可以看得出在指令ADD EAX,EBX的执行过程中,译码的结果s_eax_out、s_alu1_in、s_ebx_out、s_alu2_in、s_alu_out、s_eax_in都有效。但是它们是否起作用还受到时序信号T1、T2、T3、T4的控制。

s_eax_out、s_alu1_in、s_ebx_out、s_alu2_in的控制逻辑相当于如下逻辑表达式:

(s_eax_out、s_alu1_in、s_ebx_out、s_alu2_in)AND(T1 OR T2)

因此可以看得出s_eax_out、s_alu1_in、s_ebx_out、s_alu2_in只会在T1和T2期间有效。

s_alu_out、s_eax_in的控制逻辑相当于如下逻辑表达式:

(s_alu_out、s_eax_in)AND  T3

因此,s_alu_out、s_eax_in只会在T3期间有效,将计算的结果存入到EAX中。

由此,我们得出结论,其实我们的计算机并不知道你的语言是C、C++、VC++、VB、JAVA还是什么别的东东。它只认识机器码,也就是二进制代码。所以,对于计算机来说它并不知道高级语言的存在。这就是为什么我们需要将一个高级语言的程序经过编译、连接最后生成机器码的原因。需要指出的是,不同的CPU其机器码是不一样的。正如你将一个VC中生成的可执行代码移植到ARM CPU s3c2410中去,该程序并不能执行。你必须通过交叉编译将代码编译成ARM s3c2410的机器码,这样才能在ARM s3c2410上执行。

 

三、     我们的代码变成了什么?

下面是一个简单的程序,我们看看编译后的我们的代码变成了什么?

1、          打开VC6.0,建立一个控制台工程,命名为test

2、          在工程中新建一个源文件文件f1,输入如下程序:

        //test.cpp

            voidmain()

{

   inta;

intb;

    intc;

    a = 10;

    b = 20;

   c = a+b;

}

3、          在程序的开始处设置一个断点。(怎么设置断点大家应该都会吧,如果不会的请单独问我)按F5调试运行。

4、          选择debug工具栏的disassambly按钮如图1_2

 


图1_2 disassambly按钮

点击该按钮后,将出现我们的程序的机器码经反汇编以后的汇编语言的代码如下:

      --- E:\我代的课\c++本质论\test\f1.cpp  ----------------------------------------------------------------------------------------------------------------------------------------

1:    //f1.cpp

2:    void main()

3:    {

00401010   push        ebp

00401011   mov         ebp,esp

00401013   sub         esp,4Ch

00401016   push        ebx

00401017   push        esi

00401018   push        edi

00401019   lea         edi,[ebp-4Ch]

0040101C   mov         ecx,13h

00401021   mov         eax,0CCCCCCCCh

00401026   rep stos    dword ptr [edi]

4:        int a;

5:        int b;

6:        int c;

7:        a = 1;

00401028   mov         dword ptr [ebp-4],1

8:        b = 2;

0040102F   mov         dword ptr [ebp-8],2

9:        c = a+b;

00401036   mov         eax,dword ptr [ebp-4]

00401039   add         eax,dword ptr [ebp-8]

0040103C   mov         dword ptr [ebp-0Ch],eax

10:   }

0040103F   pop         edi

00401040   pop         esi

00401041   pop         ebx

00401042   mov         esp,ebp

00401044   pop         ebp

00401045   ret

5、          很奇怪的事情发生了,我们的a、b、c都不见了,而且代码变得我们都不认识了。但是,仔细观察一下我们会发觉这几行代码有点意思:

7:        a = 1;

00401028   mov         dword ptr [ebp-4],1

8:        b = 2;

0040102F   mov         dword ptr [ebp-8],2

9:        c = a+b;

00401036   mov         eax,dword ptr [ebp-4]

00401039   add         eax,dword ptr [ebp-8]

0040103C   mov         dword ptr [ebp-0Ch],eax

 

7:        a = 1;这是我们的代码,

00401028   mov         dword ptr [ebp-4],1这是机器码反汇编后的代码。可以看得出我们的a变成这个样子了:dword ptr [ebp-4];我们的赋值运算符”=”变成了MOV;常量”1”还是老样子“1’。而且,返汇编后的代码前面还出现了” 00401028“这么个东东。00401028是什么呢?其实就是这条机器码在内存中存储的地址。这里需要解释一下,其实00401028并不是物理地址,而是一个逻辑地址,也就是分配给每个进程的4G的虚拟内存空间地址。

自然,dword ptr [ebp-8]就是b,

      dword ptr [ebp-0Ch]就是c

那么,dword ptr [ebp-4]、dword ptr [ebp-8]、dword ptr [ebp-0Ch]又是什么呢?我在这儿简单的解释一下,其实这是局部变量a、b、c在堆栈中的存储单元,每个整形数分配四个字节的内存单元,所以你看的ebp-4、ebp-8、ebp-0c这样奇怪的东东,可以看得出每个刚好相差4个字节。关于变量编译后的详细讲解请参见第三章的内容。

 

四、     结束语

计算机并不认识c、c++之类的高级语言,高级语言必须经过编译连接后生产机器码,计算机才能执行。其实,计算机也并不知道函数、成员函数,对象这些高级语言的概念的存在。有关计算机到底是如何执行指令的,我将在下一章中详细讲解。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菜鸟来了2022

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值