将递归函数非递归化的一般方法(cont)

本文通过模拟汇编里的stack机制,构建一个自己的stack,然后将上一篇blog末尾的递归函数void bst_walk(bst_node_t *root)非递归化。

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不可访问(尤其是写操作),因为不在分配的内存有效范围内;

2. 对于入栈操作(push), 第一步是将sp-=1, 第二步是写入要入栈的元素 (*sp = E); (因为初始化后*sp的内存不可写,所以push操作一定率先改写sp)

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 Makefile

 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 编译和运行测试

$ 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为空)

注: 左图中的函数使用了两次递归,所以将其转化成非递归函数的难度相对较大。

posted @ 2017-01-14 14:15  veli  阅读(615)  评论(0编辑  收藏  举报