斐波那契数列又称黄金分割数列,其基本的迭代公式如下:

F(0)=1,F(1)=1, F(n)=F(n-1)+F(n-2)(n>=2,n∈N*)

首先用C语言写一个无限输出(255以内的)斐波那契数列出来

% cat fib.c
#include <stdio.h>

int main(void){
    int x, y, z;
    
    while (1) {
        x = 0;
        y = 1;
        do{
            printf("%d\n", x);
            
            z = x + y;
            x = y;
            y = z;
        } while (x < 255);
    }
}

其print的结果为

0
1
1
2
3
5
8
13
21
34
55
89
144
233
0
1
1
2
3
5
8
...无限循环下去

将得到的机器语言代码进行反汇编(x86-64)之后得到如下代码

fib:(__TEXT,__text) section
_main:
0000000100000f20    pushq    %rbp
0000000100000f21    movq    %rsp, %rbp
0000000100000f24    subq    $0x20, %rsp
0000000100000f28    movl    $0x0, -0x4(%rbp)
;以上对应的是c语言中初始化的过程,程序正式运行从下面代码开始

0000000100000f2f    movl    $0x0, -0x8(%rbp)
;代表将0x0(十进制0) 赋值给地址为0x8的寄存器中 =》 也就是c语言中写的 x = 0 的具体实现
;此时0x8指代的就是变量x
0000000100000f36    movl    $0x1, -0xc(%rbp)
;和上面一样,0x1(十进制1)赋值给0xc的寄存器中 相当于 y = 1
;此时0xc指代变量y
;为什么从0x8直接到0xc了呢,因为c语言中,int型变量使用4个字节来存储,因此0x8 + 4 = 0xC

;根据上面c代码,接下来应该执行的命令就是printf(x),下面4行就是对printf(x)的具体执行
0000000100000f3d    leaq    0x56(%rip), %rdi
0000000100000f44    movl    -0x8(%rbp), %esi
0000000100000f47    movb    $0x0, %al
0000000100000f49    callq    0x100000f78
;其中出现了0x8(变量x),并且进行了一些printf所需的初始化setting,最后调用0x100000f78
;也就是调用c语言标准库里面printf来输出x

;接下来应该执行的是 z = x + y,汇编使用了3行代码
0000000100000f4e    movl    -0x8(%rbp), %esi
;相当于 x -> esi 寄存器
0000000100000f51    addl    -0xc(%rbp), %esi
;相当于 add y -> esi , 此时esi保存的是x+y的值
0000000100000f54    movl    %esi, -0x10(%rbp)
;相当于 esi -> 0x10(指向变量z), 和上面一样, 0xc + 4 = 0x10
;此时0x10指代变量z

;接下来执行的是 x = y, 同样也是先 y -> esi; 再 esi -> x 
0000000100000f57    movl    -0xc(%rbp), %esi
0000000100000f5a    movl    %esi, -0x8(%rbp)

;y = z 同理, z -> esi; esi -> y
0000000100000f5d    movl    -0x10(%rbp), %esi
0000000100000f5a    movl    %esi, -0xc(%rbp)

;之后一行意义不大,c语言是根据printf函数的要求来保存一下之前printf过的值(之前打印出来的,即便用不到)
0000000100000f63    movl     %eax, -0x14(%rbp)

;之后执行的是判断x与255的大小,并且根据结果进行跳转,0xff也就是10进制的255
0000000100000f66    cmpl    $0xff, -0x8(%rbp)
0000000100000f6d    jl        0x100000f3d
;jl代表 jump if less than, 也即是如果x<255,则跳转至0x100000f3d,也就是printf那一行,开始循环
0000000100000f73    jmp        0x100000f2f
;jmp为无条件跳转,直接跳转到0x100000f2f,也就是x=0那里,重新开始循环

经过上面的解说之后,我再贴一下源汇编代码,是不是就变得非常易懂了呢?

0000000100000f20    pushq    %rbp
0000000100000f21    movq    %rsp, %rbp
0000000100000f24    subq    $0x20, %rsp
0000000100000f28    movl    $0x0, -0x4(%rbp)
0000000100000f2f    movl    $0x0, -0x8(%rbp)
0000000100000f36    movl    $0x1, -0xc(%rbp)
0000000100000f3d    leaq    0x56(%rip), %rdi
0000000100000f44    movl    -0x8(%rbp), %esi
0000000100000f47    movb    $0x0, %al
0000000100000f49    callq    0x100000f78
0000000100000f4e    movl    -0x8(%rbp), %esi
0000000100000f51    addl    -0xc(%rbp), %esi
0000000100000f54    movl    %esi, -0x10(%rbp)
0000000100000f57    movl    -0xc(%rbp), %esi
0000000100000f5a    movl    %esi, -0x8(%rbp)
0000000100000f5d    movl    -0x10(%rbp), %esi
0000000100000f5a    movl    %esi, -0xc(%rbp)
0000000100000f63    movl     %eax, -0x14(%rbp)
0000000100000f66    cmpl    $0xff, -0x8(%rbp)
0000000100000f6d    jl        0x100000f3d
0000000100000f73    jmp        0x100000f2f

补充一下汇编语言和机器语言的区别,简单来说,就是把全为2进制的0/1指令翻译成为了人能看懂意思的指令语言,并不会改变其中的指令集和机器语言的特定性。

Last modification:February 4th, 2021 at 08:59 pm