深入浅出计算机组成原理
徐文浩
bothub 创始人
70432 人已学习
新⼈⾸单¥68
登录后,你可以任选4讲全文学习
课程目录
已完结/共 62 讲
深入浅出计算机组成原理
15
15
1.0x
00:00/00:00
登录|注册

06 | 指令跳转:原来if...else就是goto

jle条件跳转指令
cmp比较指令
jne指令
cmp指令
条件码寄存器
指令寄存器
PC寄存器
跳转执行
顺序执行
汇编代码性能比较
switch...case语句
《深入理解计算机系统》
寄存器的作用
硬件层面实现goto语句
高级语言与机器指令
与条件语句类似
实现循环
跳转执行流程
编译成汇编代码
跳转执行流程
编译成汇编代码
寄存器
指令执行流程
课后思考
推荐阅读
总结
goto语句
for/while循环语句
if...else条件语句
CPU执行指令
计算机指令执行流程

该思维导图由 AI 生成,仅供参考

上一讲,我们讲解了一行代码是怎么变成计算机指令的。你平时写的程序中,肯定不只有 int a = 1 这样最最简单的代码或者指令。我们总是要用到 if…else 这样的条件判断语句、while 和 for 这样的循环语句,还有函数或者过程调用。
对应的,CPU 执行的也不只是一条指令,一般一个程序包含很多条指令。因为有 if…else、for 这样的条件和循环存在,这些指令也不会一路平铺直叙地执行下去。
今天我们就在上一节的基础上来看看,一个计算机程序是怎么被分解成一条条指令来执行的。

CPU 是如何执行指令的?

拿我们用的 Intel CPU 来说,里面差不多有几百亿个晶体管。实际上,一条条计算机指令执行起来非常复杂。好在 CPU 在软件层面已经为我们做好了封装。对于我们这些做软件的程序员来说,我们只要知道,写好的代码变成了指令之后,是一条一条顺序执行的就可以了。
我们先不管几百亿的晶体管的背后是怎么通过电路运转起来的,逻辑上,我们可以认为,CPU 其实就是由一堆寄存器组成的。而寄存器就是 CPU 内部,由多个触发器(Flip-Flop)或者锁存器(Latches)组成的简单电路。
触发器和锁存器,其实就是两种不同原理的数字电路组成的逻辑门。这块内容并不是我们这节课的重点,所以你只要了解就好。如果想要深入学习的话,你可以学习数字电路的相关课程,这里我们不深入探讨。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

计算机程序执行原理的文章深入浅出地介绍了程序如何被分解成一条条指令来执行,以及CPU如何执行指令。通过讲解CPU内部的寄存器组成和功能,以及从if...else条件语句的编译过程入手,详细解释了条件判断的汇编代码执行过程。通过简单的程序示例和对应的汇编代码,生动地展示了程序执行和跳转的过程,帮助读者深入理解计算机程序的执行原理。文章指出,无论是if...else条件语句还是for/while循环,底层执行都是通过类似于goto的方式实现的。此外,文章还提到了推荐阅读《深入理解计算机系统》的第3章,详细讲解了C语言和Intel CPU的汇编语言以及指令的对应关系。总的来说,本文适合对计算机底层执行原理感兴趣的读者阅读,能够帮助他们快速了解程序执行的基本原理和底层实现方式。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《深入浅出计算机组成原理》
新⼈⾸单¥68
立即购买
登录 后留言

全部留言(142)

  • 最新
  • 精选
  • 宋不肥
    个人理解:这一讲的核心在于理解几个寄存器的作用,从而理解cpu运行程序的过程:cpu从PC寄存器中取地址,找到地址对应的内存位子,取出其中指令送入指令寄存器执行,然后指令自增,重复操作。所以只要程序在内存中是连续存储的,就会顺序执行这也是冯诺依曼体系的理念吧。而实际上跳转指令就是当前指令修改了当前PC寄存器中所保存的下一条指令的地址,从而实现了跳转。当然各个寄存器实际上是由数电中的一个一个门电路组合出来的,而各个门电路的具体电路形式也是属于模电的东西。对于我们来说,有个具体概念就行,实在需要的时候再回去翻翻课本捡起来就行。

    作者回复: 👍完全正确。

    2019-05-08
    23
    215
  • L
    非计算机专业 表示看到这一章已经很懵逼了

    作者回复: 那要加油搞清楚啊。

    2019-05-08
    7
    76
  • 免费的人
    switch case 要看编译器有没有生成跳表,没有的话跟if else效率应该是一样的,比如case个数比较少的情况

    作者回复: 👍

    2019-05-09
    3
    50
  • Out
    老师您好,在文中您提到:“在这里,如果比较的结果是False,也就是0,就把零标志码设置为1” 这个地方是不是有问题,根据我查到结果,cmp will ZF to 1 when two operands are equal. 所以如果比较的结果是True,才会把零标志码设置为1。

    作者回复: 是得,笔误了。应该是 “如果比较得结果是True,也就是 r == 0,就把零标志码设置为1” 不然后面得jne跳转和这里也对不上。

    2019-05-09
    2
    45
  • Linuxer
    int main() { 0: 55 push rbp 1: 48 89 e5 mov rbp,rsp int i = 0; 4: c7 45 fc 00 00 00 00 mov DWORD PTR [rbp-0x4],0x0 int a = 0; b: c7 45 f8 00 00 00 00 mov DWORD PTR [rbp-0x8],0x0 switch(i) 12: 8b 45 fc mov eax,DWORD PTR [rbp-0x4] 15: 83 f8 01 cmp eax,0x1 18: 74 07 je 21 <main+0x21> 1a: 83 f8 02 cmp eax,0x2 1d: 74 0b je 2a <main+0x2a> 1f: eb 12 jmp 33 <main+0x33> { case 1: a = 1; 21: c7 45 f8 01 00 00 00 mov DWORD PTR [rbp-0x8],0x1 break; 28: eb 11 jmp 3b <main+0x3b> case 2: a = 2; 2a: c7 45 f8 02 00 00 00 mov DWORD PTR [rbp-0x8],0x2 break; 31: eb 08 jmp 3b <main+0x3b> default: a = 3; 33: c7 45 f8 03 00 00 00 mov DWORD PTR [rbp-0x8],0x3 break; 3a: 90 nop } return 1; 3b: b8 01 00 00 00 mov eax,0x1 } 40: 5d pop rbp 41: c3 ret 课后问题验证,这么看如果是单纯的两个分支采用if else更有利,另外 mov eax,0x1从这儿看象是main的返回值

    作者回复: 是的,如果没有提供返回值,很多版本的编译器会隐式地生成一个return 0;的返回值,就会生成 mov eax, 0x0 的多出来的指令。我修改一下让文章更准确一点。

    2019-05-08
    2
    27
  • 不记年
    cpu的在执行指令时还要有个转码的电路来将指令转换成不同的电信号,这些电信号可以控制各个寄存器的动作~

    作者回复: 👍这个关于CPU的控制器的译码器的部分我会在后续讲解CPU的部分讲到。

    2019-05-10
    20
  • 大熊
    接testswitch1的那条 如果条件比较多,3个以上,反汇编出来的代码就是先经过一系列计算,最后跳转。 -----------testswitch2.c #include <stdio.h> int main(int argc, char const *argv[]) { int m = 3; int a = 0; switch (m) { case 5: a = 5; break; case 4: a = 4; break; case 3: a = 3; break; case 2: a = 2; break; case 1: a = 1; break; default: a = 9; } return 0; } -----------testswitch2.c 上面的代码进行的反汇编: -----------testswitch2 #include <stdio.h> int main() { 0: 55 push rbp 1: 48 89 e5 mov rbp,rsp 4: 89 7d ec mov DWORD PTR [rbp-0x14],edi 7: 48 89 75 e0 mov QWORD PTR [rbp-0x20],rsi int m = 3; b: c7 45 f8 03 00 00 00 mov DWORD PTR [rbp-0x8],0x3 # 把3,放在地址[rbp-0x8] int a = 0; 12: c7 45 fc 00 00 00 00 mov DWORD PTR [rbp-0x4],0x0 # 把0,放在地址[rbp-0x4] switch (m) 19: 83 7d f8 05 cmp DWORD PTR [rbp-0x8],0x5 # 把[rbp-0x8]的值(即3)和5比较 1d: 77 51 ja 70 <main+0x70> # 如果大于5,则跳转到70(即default)执行 a = 9 1f: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8] # 把3,放入eax # 从这里开始不做过多解释,这就是经过一系列操作和计算的部分 22: 48 8d 14 85 00 00 00 lea rdx,[rax*4+0x0] 29: 00 2a: 48 8d 05 00 00 00 00 lea rax,[rip+0x0] # 31 <main+0x31> 31: 8b 04 02 mov eax,DWORD PTR [rdx+rax*1] # 34: 48 63 d0 movsxd rdx,eax 37: 48 8d 05 00 00 00 00 lea rax,[rip+0x0] # 3e <main+0x3e> 3e: 48 01 d0 add rax,rdx # 到这里结束 41: ff e0 jmp rax # 直接执行的是jmp指令,直接跳转到要执行的语句位置 /*后面代码省略*/ -----------testswitch2 所以,switch判断条件多,且最好case之间的差值不要过大的时候最好使用switch

    作者回复: 👍多动手自己体会是王道。

    2019-05-22
    15
  • rookie
    程序如下: int main(){ int i = 0; int a = 0; switch(i){ case 1: a = 1; break; case 2: a = 2; break; default: a = 3; break;} return 1; } 下面是机器码和汇编代码: 0000000000000000 <main>: 0: 55 push rbp 1: 48 89 e5 mov rbp,rsp 4: c7 45 f8 00 00 00 00 mov DWORD PTR [rbp-0x8],0x0 #将0复制给[rbp-0x8] 这个内存地址,即 i = 0 b: c7 45 fc 00 00 00 00 mov DWORD PTR [rbp-0x4],0x0 #将0复制给[rbp-0x4] 这个内存地址,即 a = 0 12: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8] #将i的值1复制给寄存器eax 15: 83 f8 01 cmp eax,0x1 #将eax与1进行比较,如果true则执行下面的je指令,跳转到21 18: 74 07 je 21 <main+0x21> 1a: 83 f8 02 cmp eax,0x2 #将eax与2进行比较,如果true则执行下面的je指令,跳转到2a 1d: 74 0b je 2a <main+0x2a> 1f: eb 12 jmp 33 <main+0x33> #跳转到33 21: c7 45 fc 01 00 00 00 mov DWORD PTR [rbp-0x4],0x1 #a = 1 28: eb 11 jmp 3b <main+0x3b> 2a: c7 45 fc 02 00 00 00 mov DWORD PTR [rbp-0x4],0x2 #a = 2 31: eb 08 jmp 3b <main+0x3b> 33: c7 45 fc 03 00 00 00 mov DWORD PTR [rbp-0x4],0x3 #a = 3 3a: 90 nop 3b: b8 01 00 00 00 mov eax,0x1 # eax = 1 40: 5d pop rbp 41: c3 ret # 返回eax

    作者回复: 👍

    2019-05-10
    11
  • Linuxer
    51: b8 00 00 00 00 mov eax,0x0 这个会不会是main的返回值呢?

    作者回复: Linuxer同学你说得对,这个就是main的返回值。

    2019-05-08
    11
  • aiter
    徐老师好~ C语言我不会,。,努力看了半天,算是懂了大部分,但是for循环那里还是有点问题~汇编语言里,jmp 1e 之后,应该是做比较cmp,但是为什么不是0和3比较,而是和16进制的2(0x2)比较? ————- 因为后面用的jle(jump if less or equal) <=2.如果是使用jl(jump if less) <3.应该是编译器的优化行为?可以自己写汇编代码,使用jl 0x3试试效果是否一样

    作者回复: aiter同学谢谢。我回复了,不过你这里的理解不太对,jle指令并不是和2做比较,而是判断标志位的,jle 和 jl 用的是不同的标志位,具体可以看看这个reference http://www.unixwiz.net/techtips/x86-jumps.html

    2019-05-09
    9
收起评论
显示
设置
留言
99+
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部