终端只能显示字符,不能直接显示数字 42。所以汇编里做算术只是第一步,更麻烦的是把结果「翻译」成 '4''2' 再送出去。这一篇我们练四则运算,并实现一个可复用的 print_number 子程序。

这是「x86 汇编入门」系列的第 3 篇。前两篇解决了输出字符串和读取输入。这一篇通过 03_calc.asm,掌握算术指令和整数转 ASCII 的核心技巧。

一、基本算术指令

对两个常数 428 演示四则运算:

指令 含义 示例结果
add dst, src 加法 42 + 8 = 50
sub dst, src 减法 42 - 8 = 34
imul dst, src 有符号乘法 42 × 8 = 336
idiv src 有符号除法 42 ÷ 8 = 5 … 2

除法要特别注意:idivrdx:rax 作为被除数。执行前需要 cqorax 符号扩展到 rdx

1
2
3
4
mov     rax, num_a
cqo ; rax → rdx:rax
mov rcx, num_b
idiv rcx ; rax = 商, rdx = 余数

二、程序结构:主流程 + 子程序

03_calc.asm 比前两篇多了函数雏形。主程序依次打印五组结果:

1
2
3
4
5
6
7
8
mov     rsi, msg_add
mov rdx, msg_add_len
call print_str

mov rax, num_a
add rax, num_b
call print_number
call print_newline

call 跳转到子程序,执行完 ret 回到下一条指令——下一篇会深入讲机制,这里先当「可复用的代码块」用。

三、数字转字符串的原理

人看 50,机器里是二进制。要打印出来,经典做法是反复除以 10

  1. 50 ÷ 10 → 商 5,余 0 → 字符 '0'
  2. 5 ÷ 10 → 商 0,余 5 → 字符 '5'
  3. 余数从低到高入栈,再倒序弹出输出 → "50"

核心循环:

1
2
3
4
5
6
7
8
.divide_loop:
xor rdx, rdx
div rbx ; rbx = 10
add dl, '0' ; 余数变 ASCII 数字字符
push rdx
inc rcx
test rax, rax
jnz .divide_loop

'0' 的 ASCII 码是 48,所以余数 0–9 加上 '0' 就得到 '0''9'

四、运行输出

1
2
make
./build/03_calc
1
2
3
4
5
42 + 8  = 50
42 - 8 = 34
42 * 8 = 336
42 / 8 = 5
42 % 8 = 2

五、工程上的小细节

  • 除法后余数在 rdx,调用 print_number 前用 push rdx 保存,避免被覆盖
  • print_str 约定:rsi = 地址,rdx = 长度——类似精简版调用约定
  • num_str 放在 .bss,作为逐字符输出的临时缓冲

六、小结

本篇你掌握了:

  • add / sub / imul / idivcqo 的配合
  • 除法取商和余数
  • 整数转十进制字符串的「除 10 取余 + 倒序输出」算法
  • call / ret 组织可复用代码(预告下一篇)

x86 汇编入门系列第 3 篇完。下一篇用条件跳转和循环,让程序重复执行——打印 1 到 10。