將遞歸函數非遞歸化的通常方法(cont)

本文經過模擬彙編裏的stack機制,構建一個本身的stack,而後將上一篇blog末尾的遞歸函數void bst_walk(bst_node_t *root)非遞歸化。node

o libstack.h函數

 1 #ifndef _LIBSTACK_H
 2 #define _LIBSTACK_H
 3 
 4 #ifdef    __cplusplus
 5 extern "C" {
 6 #endif
 7 
 8 typedef void * uintptr_t; /* generic pointer to any struct */
 9 
10 uintptr_t *stack_init(size_t size);
11 void stack_fini();
12 int stack_isFull();
13 int stack_isEmpty();
14 void push(uintptr_t e);
15 void pop(uintptr_t *e);
16 
17 #ifdef    __cplusplus
18 }
19 #endif
20 
21 #endif /* _LIBSTACK_H */

o libstack.c測試

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include "libstack.h"
 4 
 5 /**
 6  * Basic Stack OPs are supported, including:
 7  *
 8  * 1. Construct/Destruct a stack
 9  * 2. Tell stack is full or empty
10  * 3. push() and pop()
11  *
12  * == DESIGN NOTES ==
13  *
14  * There are 3 static variables reserved,
15  *
16  *     ss: stack segment
17  *     sp: stack pointer
18  *     sz: stack size
19  *
20  * And the stack looks like:
21  *
22  *               | RED | ss[-1]  ; SHOULD NEVER BE ACCESSED
23  *     low-addr  +-----+ <------------TopOfStack-----------
24  *        ^      |     | ss[0]
25  *        |      |     | ss[1]
26  *        |      | ... |
27  *        |      |     |
28  *        |      |     | ss[sz-1]
29  *        |      +-----+ <------------BottomOfStack--------
30  *     high-addr | RED | ss[sz]  ; SHOULD NEVER BE ACCESSED
31  *
32  * (1) If (sp - ss) == 0,  stack is full
33  * (2) If (sp - ss) == sz, stack is empty
34  * (3) Push(E): { sp -= 1;   *sp = E; }
35  * (4) Pop(&E): { *E  = *sp; sp += 1; }
36  */
37 
38 static uintptr_t *ss = NULL; /* stack segment */
39 static uintptr_t *sp = NULL; /* stack pointer */
40 static size_t sz = 0;        /* stack size */
41 
42 int stack_isFull()  { return (sp == ss); }
43 int stack_isEmpty() { return (sp == ss + sz); }
44 
45 uintptr_t *
46 stack_init(size_t size)
47 {
48     ss = (uintptr_t *)malloc(sizeof (uintptr_t) * size);
49     if (ss == NULL) {
50         fprintf(stderr, "failed to malloc\n");
51         return NULL;
52     }
53 
54     sz = size;
55     sp = ss + size;
56     return ss;
57 }
58 
59 void
60 stack_fini()
61 {
62     free(ss);
63 }
64 
65 void
66 push(uintptr_t e)
67 {
68     sp -= 1;
69     *sp = e;
70 }
71 
72 void
73 pop(uintptr_t *e)
74 {
75     *e = *sp;
76     sp += 1;
77 }

1. 一旦棧被初始化後,棧指針sp必定是指向棧底,*sp不可訪問(尤爲是寫操做),由於不在分配的內存有效範圍內;ui

2. 對於入棧操做(push), 第一步是將sp-=1, 第二步是寫入要入棧的元素 (*sp = E); (由於初始化後*sp的內存不可寫,因此push操做必定率先改寫sp)spa

3. 對於出棧操做(pop), 順序與push相反,第一步取出sp指向的內存地址裏的內容(E = *sp), 第二步纔是將sp+=1;指針

o foo.c (簡單測試)調試

 1 /**
 2  * A simple test against stack OPs, including:
 3  * o stack_init(),   stack_fini()
 4  * o stack_isFull(), stack_isEmpty()
 5  * o push(), pop()
 6  */
 7 
 8 #include <stdio.h>
 9 #include "libstack.h"
10 
11 static void
12 dump_stack(uintptr_t *ss, size_t size)
13 {
14     (void) printf("%p: ", ss);
15     for (int i = 0; i < size; i++) {
16         if (ss[i] != NULL)
17             (void) printf("%-10p ", *(ss+i));
18         else
19             (void) printf("0x%-8x ", 0x0);
20     }
21     printf("\n");
22 }
23 
24 int
25 main(int argc, char *argv[])
26 {
27     size_t size = 4;
28 
29     uintptr_t *ss = stack_init(size);
30     dump_stack(ss, size);
31 
32     for (int i = 0; !stack_isFull(); i++) {
33         push((uintptr_t)(ss+i));
34         dump_stack(ss, size);
35     }
36 
37     (void) printf("\n");
38 
39     uintptr_t e = NULL;
40     for (; !stack_isEmpty();) {
41         pop(&e);
42         (void) printf(" (pop) got %-10p\n", e);
43     }
44 
45     stack_fini();
46 
47     return 0;
48 }

o Makefilecode

 1 CC      = gcc
 2 CFLAGS  = -g -Wall -std=gnu99 -m32
 3 INCS    =
 4 
 5 TARGET  = foo
 6 
 7 all: ${TARGET}
 8 
 9 foo: foo.o libstack.o
10     ${CC} ${CFLAGS} -o $@ $^
11 
12 foo.o: foo.c
13     ${CC} ${CFLAGS} -c $< ${INCS}
14 
15 libstack.o: libstack.c libstack.h
16     ${CC} ${CFLAGS} -c $<
17 
18 clean:
19     rm -f *.o
20 clobber: clean
21     rm -f ${TARGET}

o 編譯和運行測試blog

$ make
gcc -g -Wall -std=gnu99 -m32 -c foo.c
gcc -g -Wall -std=gnu99 -m32 -c libstack.c
gcc -g -Wall -std=gnu99 -m32 -o foo foo.o libstack.o

$ ./foo
0x8ecc008: 0x0        0x0        0x0        0x0
0x8ecc008: 0x0        0x0        0x0        0x8ecc008
0x8ecc008: 0x0        0x0        0x8ecc00c  0x8ecc008
0x8ecc008: 0x0        0x8ecc010  0x8ecc00c  0x8ecc008
0x8ecc008: 0x8ecc014  0x8ecc010  0x8ecc00c  0x8ecc008

 (pop) got 0x8ecc014
 (pop) got 0x8ecc010
 (pop) got 0x8ecc00c
 (pop) got 0x8ecc008
$

測試簡單且直接,不解釋。若是還不確信,能夠用gdb調試。遞歸


如今對上一篇blog末尾的遞歸函數使用上面實現的stack進行去遞歸化改寫,改寫後的代碼以下:

 1 void
 2 bst_walk(bst_node_t *root)
 3 {
 4     if (root == NULL)
 5         return;
 6 
 7     (void) stack_init(STACK_SIZE);
 8 
 9     while (root != NULL || !stack_isEmpty()) {
10         if (root != NULL) {
11             push((uintptr_t)root);
12             root = root->left;
13             continue;
14         }
15 
16         pop((uintptr_t *)(&root));
17         printf("%d\n", root->key);
18 
19         root = root->right;
20     }
21 
22     stack_fini();
23 }

爲方便閱讀,下面給出使用meld進行diff後的截圖,

  • L7: 構建一個stack, 其中STACK_SIZE是一個宏
  • L22: 將stack銷燬
  • L9-14: 首先遍歷左子樹,不斷將結點壓入棧中,直到到達最左的葉子結點,那麼則執行L16-17 (最左的葉子結點也會被壓入棧中)
  • L16-17: 出棧並打印結點的key
  • L19: 將新的根結點設置爲剛剛出棧的結點的右兒子, 從新執行L9-17, 直到全部結點都被遍歷到(固然, stack爲空)

注: 左圖中的函數使用了兩次遞歸,因此將其轉化成非遞歸函數的難度相對較大。

相關文章
相關標籤/搜索