|
|
本文成篇的起因于社长的一条记录 链接
" x4 G7 u# C+ V4 q; Z. Q% \% `4 h2 d% q( M1 |
递归是计算机科学中一种解决问题的重要方法。所谓递归,通俗地说就是自己调用自己,它在很多语言和环境下都可以实现。以社长的工作场景为例,根据爱坛上的信息,大概是两个关键词:Unix和汇编。本人条件有限,只能用Linux模拟,汇编也只限于x86汇编。下面就是用汇编语言编写的,用递归方法计算阶乘的示范程序。0 Q2 F8 c8 ^1 W4 {
- LEN_OF_OUTPUT equ 104 w& Y' o; y) a: }
- ! \; d: G8 i- v% H0 {& L
- section .data ;# j/ J5 t7 t7 R- S0 e
- userMsg db 'Please enter a single-digit number: ' ;
; f3 t. ^3 M, ~' |, r - lenUserMsg equ $-userMsg ;& m% D2 U. E# y
- disp1Msg db 'The factorial of '
8 P6 o, ?: A2 j$ G' S% P/ l$ N - lenDisp1Msg equ $-disp1Msg
+ v& p8 J( j) E" Y5 b - disp2Msg db ' is '2 N$ j- |: P5 b0 y; _
- lenDisp2Msg equ $-disp2Msg
: ~* a- J9 I0 k6 ~& q: y - errMsg db 'Wrong input. Only one-digit number can be accepted.',0x0a4 |+ z1 l% B& {- O3 Y6 o- ?
- lenErrMsg equ $-errMsg 6 N* d' d3 p' s% W C) }! D
' q/ k& X( u5 [/ R7 t- section .bss ;7 h' ~/ @8 e( {* ~9 d& a
- num resb 1% W0 U) q! @/ [: U$ c- ~0 |2 _
- result resd 1
2 w7 l5 s) R# H - buffer resb LEN_OF_OUTPUT+14 d9 {( t$ q ]6 m3 n
- ' V$ R7 x0 T7 P1 q2 F+ S4 i8 {
- section .text ;1 M% F9 A7 q, Z T& M9 m0 m& t7 `
- global _start+ c! q: o S4 |5 l+ i
-
6 c5 o( k& T$ m; R, l - _start: ;
7 b& x9 }7 n7 d& L& F5 l - mov eax, 42 b1 k' z. k4 `, ]; t6 @9 v- b
- mov ebx, 16 {7 j( e/ N, d3 I+ p' o! p
- mov ecx, userMsg
7 B$ W0 J- W5 X' K2 {* X9 z - mov edx, lenUserMsg
& ~( t* y0 N' f+ A6 ~* b; s - int 80h4 d7 O2 S& z F4 ^+ l7 d1 c- ^0 u
- - h7 C. H1 }+ P+ D) A
- ; read user's input
- c, M. |! X( f8 m, W - mov eax, 3( V1 s; i6 @$ }, q2 H/ w
- mov ebx, 0( }7 B' c/ _, b2 k2 V
- mov ecx, num
; `# E) W/ } i' i- | - mov edx, 1 ; length of input buffer
4 I) ~+ r: v, S3 g+ h$ a8 e - int 80h( y; C2 }8 I/ L% z
- $ F% ^5 s. s; t( F
- mov eax, [num]
2 B: a/ e0 i+ W p# h, R - and eax, 0xFF' H! N5 n" e3 a- g5 J
- sub eax, 0x30
& U; @0 q6 S" U2 \$ { - jl ERROR7 |6 f$ T$ ~% z) G) I9 z( Z. [
- cmp eax, 10( o! ~0 a q1 Q- |
- jl DO_CALC. W W* D8 S3 s7 U+ N
- ERROR:
x3 r& Y% k9 U8 E+ i - mov eax, 4
! i$ W% b9 @. Z# W% O5 r7 K - mov ebx, 1& g8 |0 W9 Y, g8 `
- mov ecx, errMsg% o3 F2 F% G, d7 k0 V
- mov edx, lenErrMsg
3 q: x7 l- C( V8 Q1 e. k; A - int 80h
, B; a; E; {8 O6 G# \1 ?: J - jmp EXIT
- c: Q$ O- q" X$ I - % X# g0 @2 c" f6 M& B) D
- ) {5 I* I" P- z1 N7 ]" ?
- DO_CALC:$ p1 I3 J6 a6 Q! l8 X& d s5 H8 {
- call factorial
- l' K& n* D4 r( i, H - ; The parameter is in eax. After calling, the result will be stored in eax.
" l. ~2 k$ o5 r# ~ ?6 X+ e - mov [result], eax ; Save the result
- V: ?: |1 _+ y" K0 F
9 i) z8 Q# m/ y: o. |- ; print result7 D! Q; p8 C7 j6 G& V
- mov eax, 4
/ K: A6 O+ _) X( O" q - mov ebx, 1
- o9 \+ e9 Q0 v& [ - mov ecx, disp1Msg8 P* S F. m! H$ g. m5 l% t A6 l a
- mov edx, lenDisp1Msg( o, X9 L2 O9 a+ j
- int 80h 3 n Q+ R3 V. U0 l# O( _/ ]
- mov eax, 4" ]. V2 l6 @$ M h% Z0 g$ h
- mov ebx, 1
- I/ F. X! r0 C7 K. @9 x - mov ecx, num
! Q4 z* e% l3 C - mov edx, 1 |+ i1 J- S: W8 K; ~ n7 K6 j
- int 80h
' R1 p6 |$ y0 u5 s: O3 r+ V: D c - mov eax, 4
3 r5 J" T. f' g" f$ S& R" d - mov ebx, 1
: t2 K g$ C/ I# o - mov ecx, disp2Msg8 p) o. `7 ^. O( _: e- z: g0 ^
- mov edx, lenDisp2Msg
( @" I8 Y1 W1 c/ P! W - int 80h
* l/ N. G6 i7 B- m2 ? q ^ - ; B* W' g' y4 u) d1 t1 a
- ; output number a3 {3 K U- L1 G2 W
- mov eax, [result] ; VALUE THAT I WANT TO CONVERT AND DISPLAY! N/ G) ?. f( Q, T- H5 e
- mov byte [buffer+LEN_OF_OUTPUT],0x0a t5 a" j" e6 q+ E$ |$ X! Q2 \
- lea esi,[buffer+LEN_OF_OUTPUT]. \7 U3 s# G' e
- mov ebx,10 $ y% V/ D4 c- @ b1 g
- ASC_LOOP:* Q( N# M- F- K7 ]
- mov edx,0 ; clear dx prior to dividing edx:eax by bx2 }! A$ g" Y) s/ d3 j
- div ebx ;DIV EAX/10
) T0 b6 h/ Z" I& R$ u) E - add edx,0x30 ;ADD 48 TO REMAINDER TO GET ASCII CHARACTER OF NUMBER
0 p8 H5 \- W* \1 P1 E - dec esi ; store characters in reverse order
1 X+ {" N0 n+ H8 u - mov [esi],dl
5 s6 ~$ W" P5 g4 }& Y: }( W" P - cmp eax,0 # P; ?: E- I" b( u5 L: i& B
- jz END_ASC ;IF AX=0, END THE PROCEDURE OF NUMBER TO ASCII( |, M% }$ E& G
- jmp ASC_LOOP ;ELSE REPEAT
s/ r3 f$ H' R& k% f - END_ASC:/ N8 x7 h1 S8 H7 I R
- mov eax, 4 n' }5 z8 j- |* ?; u6 }
- mov ebx, 1
" M; f% a2 f/ c! D ^; Y - mov ecx, esi
$ l \3 G# m. z, V9 k6 n - lea edx,[buffer+LEN_OF_OUTPUT+1]
+ N0 Z* n3 T+ D4 M - sub edx, ecx0 h' y/ p( _# b' `
- int 80h
3 `2 w" J6 [. U6 @: d - , a/ E& K7 M& W3 n5 d8 g' b" |6 f
- ; Exit code1 W9 [: c' k& T# M
- EXIT:" B) ?% N( _5 k9 z0 y
- mov eax, 1
6 _# O- ]+ ?' [ - mov ebx, 0/ e2 c" C8 e+ V! h8 \7 z
- int 80h! w3 a$ a& _; t* z% d( U7 l
- * n! H, {& S' s6 C
( r) V4 E9 X; O7 v" p5 N2 L
2 y0 R8 s7 A" l- ; Factorial function using recursion
" J; z5 f5 [* H* L/ { - factorial:
& E, l/ R# [* o% V2 _/ R - ; Check if eax is 0 (base condition)
& E/ T& f; `5 m5 ]6 N - cmp eax, 0
) w3 m, ]; g- \7 x8 P - jz end_recursion
2 j! a% G( P/ k" {$ y
. a( l: g ], W$ |" L) O" N- ; Save the current value of eax
/ _ C2 H, w8 g6 E. M# Y - push eax
6 F2 I3 g1 K( d
& A4 X% A0 p! n# Z0 J2 c6 P+ O& C1 a- ; Decrement eax and call factorial function recursively9 o5 q! z: d. [4 S6 C/ G4 S
- dec eax
^! V$ i8 u6 _' A5 a; W' S9 i - call factorial2 y* g7 v) \' ^" S
: ~) U8 F! U2 {- ; Multiply the result returned in eax with the saved value of eax n+ K/ J) d @6 `/ }6 l! `' N
- pop ebx! q- ]3 v7 k0 y/ t$ U$ f
- imul eax, ebx
" x0 }! e* l k: k5 W0 u
# S, U: b! ^3 y0 B# H- ret
. O4 s6 f5 Z% X - . } s* S7 w5 @) o% w$ v! P& @( j
- end_recursion:* T( S( |3 R5 Y4 E, m2 X/ _, j+ T
- ; Return 1 when eax is 0 (base condition)0 o9 H* ]1 M" A. m7 _: }, Z
- mov eax, 1% }3 L, e1 ^% r2 s/ K- [
- ret
复制代码
4 s* a/ O- _, w" _8 P" W程序在nasm编译器下通过并成功运行。相应的命令行如下,有兴趣的童鞋可以自行验证。
" h6 p6 r# C" a. Z5 z: F$ ]: S) {+ s- nasm -f elf factorial.asm
- @9 `- c8 a% W3 C1 P - ld -m elf_i386 -s -o factorial factorial.o, G1 }' {( t6 O
- ./factorial
$ e6 a% k2 x6 v% O2 c0 P
复制代码 % L7 j4 _. [3 M5 G' p u8 W0 b
由于汇编不擅长处理I/O,所以程序限制输入仅限于一位数字(0-9),以免过于喧宾夺主。其实程序中真正紧扣主题的就最后一小段:
" F6 U; [5 R3 C3 t$ p- ; Factorial function using recursion
0 |8 K& U7 o8 P: \7 T6 _ - factorial:
& M* m8 v8 P5 [; o- n1 C3 U - ; Check if eax is 0 (base condition)9 B7 Z, Z4 Q- A% N$ O4 t
- cmp eax, 0$ h) _3 @. j5 ^5 G ?3 m( Q1 b! z
- jz end_recursion
& Z) s8 J& f3 S! W3 e( q
% z' W" W% O! Y0 D3 N1 C- ; Save the current value of eax, ~& T3 G6 @$ o: Y
- push eax
7 Y. g6 ? k- o: u8 J - 6 D% T& t8 H# H1 q6 @
- ; Decrement eax and call factorial function recursively
/ H& d: w0 Z! }7 A9 G, X+ C* v - dec eax
: f4 h+ z% W, x4 W# G) D0 I' k& G - call factorial
2 w& M h: ]- ^: d9 G# q
. q; p/ P3 z1 F8 p. I- ; Multiply the result returned in eax with the saved value of eax
, I _) k _* A. a - pop ebx
4 p. \# N& }4 J6 o, S - imul eax, ebx, m% w& t5 |5 |, x/ c3 S X
# j( ]6 }$ m8 m9 z9 ]" Y/ K# _- ret
8 ^" o9 @! } P' E. V% G% m9 P
S0 B- v. `, F! q- v4 ^% l- end_recursion:
) r; h! C8 |5 V+ D3 S: \3 h0 D8 S* y. ] - ; Return 1 when eax is 0 (base condition)
, E1 p T+ `: D) I7 }4 y F - mov eax, 19 Q; a# L( N# G$ R
- ret
复制代码 可以清晰看出函数在其函数体中调用了它本身。
# @0 p, H2 q' p( a! M( q! j. w* Z% T- U# c
以上证明了汇编语言不是实现递归的障碍。推而广之,所有中低级语言都可以轻松实现递归。某些高级语言反而不能做递归是人为强制规定的,换而言之是原作者权衡利弊的结果。5 h, F7 ]# S" A8 o
' H- A# a) m8 J+ X! M) ?
世界上没有免费的午餐,递归在编写程序的时候简洁优雅,但运行过程中要付出极大的代价,甚至有可能是灾难。由于是函数反复调用自己,那么所有的中间结果都要暂时保留在栈(stack)上,直到最后一层函数调用完毕才能逐步清除。这既低效,又不安全。栈式计算是一个古老的概念,它与现代的多寄存器和流水线等都格格不入,难以优化提速。递归对栈空间的占用更可怕,占用量与调用函数次数线性相关(套用复杂度的概念就是O(n)),递归次数多了之后,很容易超出栈容量,从而导致栈溢出。具体到社长的工作中可能还有很多实时任务,一大堆高优先级的中断响应程序可不会等着递归函数运行完了再启动,这会让栈空间雪上加霜。想一想某个核电控制程序突然栈溢出了,那场面,哈哈哈。
. ^: R" ^' P3 @( w( o: j
7 z7 U' o; k ]+ L$ ?. ^这可以解释,为什么条件上有可能,但社长很长时间里都没有接触到递归编程。无论用C还是用汇编,都强调的是速度与可靠性。社长实战出发,不需要知道回字的4种写法。至于Python,整个编程思想都变了,Python假定计算机的计算速度远远超过了需求,编程者无需为性能而操心。 Python语言的设计初衷,也不是为关键任务而生,而是想让更多的门外汉用起计算机来。所以在Python程序中容易见到递归。
4 D! l/ @' w$ i* P0 G+ P B E7 N$ n$ _0 D
+ E9 H; C. a$ K1 M有很多时候,递归用作概念性算法表达。真到了用代码实现的时候,再换用其它方法。比如说将递归转化成迭代(iteration)。2 W! H, C- X( v1 p4 Y3 R, }& s2 A$ C
% q8 q. W; v( I) }9 w另外,汇编语言作为一种低级编程语言,在不考虑时间成本的前提下,没有什么是它做不了的。本期课后作业:如何用汇编语言实现继承(Inheritance)和多态(Polymorphism)?请写出示范程序。我们知道继承和多态是面向对象编程(Object-oriented programming)的两个基本点,有了它们就可以实现汇编语言的面向对象编程。- [' ?' m5 B4 T; C* O h. I: K
! c6 e3 P4 G- H _0 b2 r/ z/ m6 R+ n |
评分
-
查看全部评分
|