zoukankan      html  css  js  c++  java
  • 深入浅出计算机组成原理学习笔记:第六讲

    一、CPU 是如何执行指令的?

    1、软件程序员的理解

    写好的代码变成了指令之后、是一条条顺序执行的就可以了

    2、CPU的逻辑组成

    3、寄存器

    N 个触发器或者锁存器,就可以组成一个 N 位(Bit)的寄存器,能够保存 N 位的数据。比方说,我们用的 64 位 Intel 服务器,寄存器就是 64 位的。

     

     4、特殊寄存器

    5、CPU执行指令流程

    1、CPU会根据PC寄存器里的地址,从内存里面把需要执行的指令读取到指令寄存器里面直面执行

    2、然后根据指令长度自增、开始顺序读取下一条指令。可以看到一个程序的一条条指令在内存里面是连续保存的。也会一条条顺序加载

    3、而有些特殊指令(J类跳转指令)、会修改寄存器里面的地址

    4、这样下一条要执行的指令就不是从内存里面顺序加载的

    5、事实上、这些跳转指令存在,也就是我们在写程序的时候,使用了 if…else 条件语句和 while/for 循环语句的原因

    二、从 if…else 来看程序的执行和跳转

    [root@luoahong c]# cat test.c
    #include <time.h>
    #include <stdlib.h>
    
    
    int main()
    {
      srand(time(NULL));
      int r = rand() % 2;
      int a = 10;
      if (r == 0)
      {
        a = 1;
      } else {
        a = 2;
      }
    

    我们用 rand 生成了一个随机数 r,r 要么是 0,要么是 1。当 r 是 0 的时候,我们把之前定义的变量 a 设成 1,不然就设成 2。

    [root@luoahong c]# gcc -g -c test.c
    test.c: In function ‘main’:
    test.c:15:3: error: expected declaration or statement at end of input
       }
       ^
    [root@luoahong c]# cat -n test.c
         1	#include <time.h>
         2	#include <stdlib.h>
         3
         4
         5	int main()
         6	{
         7	  srand(time(NULL));
         8	  int r = rand() % 2;
         9	  int a = 10;
        10	  if (r == 0)
        11	  {
        12	    a = 1;
        13	  } else {
        14	    a = 2;
        15	  }
    

    执行报错,是因为少了一个}

    [root@luoahong c]# cat test.c
    #include <time.h>
    #include <stdlib.h>
    
    
    int main()
    {
      srand(time(NULL));
      int r = rand() % 2;
      int a = 10;
      if (r == 0)
      {
        a = 1;
      } else {
        a = 2;
      }
    }
    
    [root@luoahong c]# gcc -g -c test.c
    [root@luoahong c]# objdump -d -M intel -S test.o
    
    test.o:     file format elf64-x86-64
    
    
    Disassembly of section .text:
    
    0000000000000000 <main>:
    #include <time.h>
    #include <stdlib.h>
    
    
    int main()
    {
       0:	55                   	push   rbp
       1:	48 89 e5             	mov    rbp,rsp
       4:	48 83 ec 10          	sub    rsp,0x10
      srand(time(NULL));
       8:	bf 00 00 00 00       	mov    edi,0x0
       d:	e8 00 00 00 00       	call   12 <main+0x12>
      12:	89 c7                	mov    edi,eax
      14:	e8 00 00 00 00       	call   19 <main+0x19>
      int r = rand() % 2;
      19:	e8 00 00 00 00       	call   1e <main+0x1e>
      1e:	99                   	cdq
      1f:	c1 ea 1f             	shr    edx,0x1f
      22:	01 d0                	add    eax,edx
      24:	83 e0 01             	and    eax,0x1
      27:	29 d0                	sub    eax,edx
      29:	89 45 fc             	mov    DWORD PTR [rbp-0x4],eax
      int a = 10;
      2c:	c7 45 f8 0a 00 00 00 	mov    DWORD PTR [rbp-0x8],0xa
      if (r == 0)
      33:	83 7d fc 00          	cmp    DWORD PTR [rbp-0x4],0x0
      37:	75 09                	jne    42 <main+0x42>
      {
        a = 1;
      39:	c7 45 f8 01 00 00 00 	mov    DWORD PTR [rbp-0x8],0x1
      40:	eb 07                	jmp    49 <main+0x49>
      } else {
        a = 2;
      42:	c7 45 f8 02 00 00 00 	mov    DWORD PTR [rbp-0x8],0x2
      }
    }
      49:	c9                   	leave
      4a:	c3                   	ret

    可以看到,这里对于 r == 0 的条件判断,被编译成了cmp 和 jne 这两条指令。cmp 指令比较了前后两个操作数的值,这里的 DWORD PTR 代表操作的数据类型是 32 位的整数

    而 [rbp-0x4] 则是一个寄存器的地址。所以,第一个操作数就是从寄存器里拿到的变量 r 的值。第二个操作数 0x0 就是我们设定的常量 0 的 16 进制表示。cmp 指令的比较结果,会存入到条件码寄存器当中去。

    在这里,如果比较的结果是 True,也就是 r == 0,就把零标志条件码(对应的条件码是 ZF,Zero Flag)设置为 1。除了零标志之外,Intel 的 CPU 下还有进位标志(CF,Carry Flah)
    符号标志(SF,Sign Flag)以及溢出标志(OF,Overflow Flag),用在不同的判断条件下。


    cmp 指令执行完成之后,PC 寄存器会自动自增,开始执行下一条 jne 的指令。

    三、如何通过 if…else 和 goto 来实现循环?

    [root@luoahong c]# cat test.c
    int main()
    {
        int a = 0;
        for (int i = 0; i < 3; i++)
        {
            a += i;
        }
    }
    
    [root@luoahong c]# gcc -g -c test.c
    test.c: In function ‘main’:
    test.c:4:5: error: ‘for’ loop initial declarations are only allowed in C99 mode
         for (int i = 0; i < 3; i++)
         ^
    test.c:4:5: note: use option -std=c99 or -std=gnu99 to compile your code
    

    错误:使用gcc编译代码会报错:

    原因:这是因为gcc是基于c89标准,不能直接在for循环中初始化增量。而C99标准可以在for循环内定义变量。

    解决方法:

     我们再看一段简单的利用 for 循环的程序。我们循环自增变量i 三次,三次之后,i>=3,就会跳出循环。整个程序,对应的 Intel 汇编代码就是这样的:

    [root@luoahong c]# objdump -d -M intel -S test.o
    
    test.o:     file format elf64-x86-64
    
    
    Disassembly of section .text:
    
    0000000000000000 <main>:
    int main()
    {
       0:	55                   	push   rbp
       1:	48 89 e5             	mov    rbp,rsp
        int a = 0;
       4:	c7 45 fc 00 00 00 00 	mov    DWORD PTR [rbp-0x4],0x0
        int i;
        for (i = 0; i < 3; i++)
       b:	c7 45 f8 00 00 00 00 	mov    DWORD PTR [rbp-0x8],0x0
      12:	eb 0a                	jmp    1e <main+0x1e>
        {
            a += i;
      14:	8b 45 f8             	mov    eax,DWORD PTR [rbp-0x8]
      17:	01 45 fc             	add    DWORD PTR [rbp-0x4],eax
        for (i = 0; i < 3; i++)
      1a:	83 45 f8 01          	add    DWORD PTR [rbp-0x8],0x1
      1e:	83 7d f8 02          	cmp    DWORD PTR [rbp-0x8],0x2
      22:	7e f0                	jle    14 <main+0x14>
        }
    }
      24:	5d                   	pop    rbp
      25:	c3                   	ret

    可以看到,对应的循环也是用 1e 这个地址上的 cmp 比较指令,和紧接着的 jle 条件跳转指令来、实现的。主要的差别在于,这里的 jle 跳转的地址,在这条指令之前的地址 14,而非 if…else 编
    译出来的跳转指令之后。往前跳转使得条件满足的时候,PC 寄存器会把指令地址设置到之前执行过的指令位置,重新执行之前执行过的指令,直到条件不满足,顺序往下执行 jle 之后的指令,整个循环才结束。

    其实,你有没有觉得,jle和jmp指令,有点像逻辑程序里面的goto命令,直接指定了一个特定条件下的跳转位置

  • 相关阅读:
    Oracle创建表空间、创建用户以及授权、查看权限
    zf2 数据库连接
    ZF2.0用户向导 —— 6. 数据库及模型(Models)
    zf2配置
    zend framework2使用教程【一】安装
    config/application.config.php
    zf2\config\application.config.php
    zf2 数据库album demo
    albumController.php
    登录
  • 原文地址:https://www.cnblogs.com/luoahong/p/10862085.html
Copyright © 2011-2022 走看看