- 1 -

有一天,有一个程序员叫做 Bob,他写了一个用 Go 语言实现的阶乘函数。但是,当数据规模变大时,运行速度非常慢。于是,他的老板让他改成用汇编语言实现。Bob 很不情愿,但还是学习了汇编语言,并写出了一个能跑得飞快的阶乘函数。

最后,Bob 成为了一名出色的汇编程序员,并因为他的阶乘函数而获得了巨大的成功。他明白,汇编语言可以让他的程序更快、更简洁。

所以,如果你想写出高效、简洁的程序,就要学习汇编语言。


- 2 -

咱们先从简单的入手,用 Go 实现一个完整的阶乘函数:

package main


import "fmt"


func factorial(n int) int {
    result := 1
    for i := 1; i <= n; i++ {
        result *= i
    }
    return result
}


func main() {
    fmt.Println(factorial(5))  // Output: 120
}

- 3 -

接下来,我们把 factorial 函数转换成汇编语言。

首先,我们要在 Go 代码里将 factorial 函数的函数体移除,只包含函数定义(声明),这告诉 Go 编译器,该函数会在另一个文件里由汇编实现。

package main


import "fmt"


func factorial(n int) int


func main() {
    fmt.Println(factorial(5))
}

然后,我们新建一个文件 fac.s,内容如下:

TEXT ·factorial(SB), $0-8
    MOVQ n+0(FP), CX
    MOVQ $1, DX
LOOP:
    IMULQ CX, DX
    DECQ CX
    JNZ LOOP
    MOVQ DX, result+8(FP)
    RET

编译运行:

$ go run . 

120


- 4 -

这段代码是一个用汇编语言实现的阶乘函数。它的作用是计算给定数字 n 的阶乘。

它的实现方式是使用一个无限循环来计算阶乘。在循环的每一次迭代中,它将结果与当前的数字相乘,然后将当前的数字减 1。最后,它将结果存储到 result 变量中,并退出函数。

接下来咱们逐句分析这段代码:

  1. TEXT ·factorial(SB), $0-8

    1. TEXT 表示这个方法在 TEXT 段中

    2. · 是Unicode的「中点」(中文输入法,1左边的按键),前面省略了包名,表示这是 main 包的 factorial 函数

    3. SB 是 stack base pointer,Go ASM 中的「伪寄存器」(不是硬件寄存器),大致等同于程序的起始地址

    4. $0-8:0 表示这个函数没有局部变量,8 表示返回值占用8个字节

  2. MOVQ n+0(FP), CX

    1. MOVQ 的 Q 表示 8 个字节

    2. n+0(FP) 表示变量 n 在 FP(Frame Pointer,伪寄存器,表示这个函数的栈帧起始位置) + 0 的位置(即第一个参数)。注意 Go ASM 要求形式上必须是「变量名+偏移量(FP)」这个写法,但是变量名n没有实际意义,只是用来助记。

    3. CX 即 x86/x86_64 的 CX(16bit),ECX(32bit),RCX(64bit) 寄存器,具体多长取决于前面的指令(MOVQ是64bit)

    4. 这句的意思是把第一个参数的值写入 RCX

  3. MOVQ $1, DX

    1. $1:$开头的是立即数

    2. 这句的意思是给 RDX 赋值为 1

  4. IMULQ CX, DX

    1. DX = DX * CX

  5. DECQ CX

    1. CX = CX - 1

  6. JNZ LOOP

    1. JNZ: Jump if Not Zero

    2. 当CX 不等于 0 时跳转到 LOOP

  7. MOVQ DX, result+8(FP)

    1. 将 RDX 的值写入到 FP+8 的位置。

  8. RET

    1. 返回到调用方。


- 5 -

需要注意的是,为了实现上更简洁,这段汇编代码和Go代码并不是等价的。

如果输入的 n 为负数,会导致代码出错。这是因为,这段代码中没有判断边界条件,所以如果输入的 n 为负数,就会无限循环下去,造成程序运行时间过长或者程序崩溃。


- 6 -

最后,这篇文章是 ChatGPT 和我一起完成的。

细心如你,应该发现了哪些是它的贡献,以及它犯的一个错误。

p.s. 题图由 6pen.art 生成,关键词「Golang 汇编语言 阶乘」。

参考:

[1] Golang ASM 简明教程:https://jiajunhuang/articles/2020_04_22-go_asm.md.html

[2] A Quick Guide to Go's Assembler:https://go.dev/doc/asm

更多推荐

讲一个故事说明使用汇编语言的必要性