由於 8051 單晶片內部的堆疊空間有限,所以無法像 windows/unix 那樣使用堆疊。一般在 C 語言裏,呼叫函數時會將函數參數及函數使用的局部變數放入堆疊。因為 8051 堆疊空間有限,所以無法使用此方式,而是為每個函數的局部變數及參數設定一個空間來存放。比如下面的範例就是如此,參數 a 及 b 被分配到二個全域變數 _test_a_1_1 及 _test_PARM_2。
541 ;------------------------------------------------------------
542 ;Allocation info for local variables in function 'test'
543 ;------------------------------------------------------------
544 ;b Allocated with name '_test_PARM_2'
545 ;a Allocated with name '_test_a_1_1'
546 ;------------------------------------------------------------
547 ; ../source/main.c:60: int test(int a, int b)
548 ; -----------------------------------------
549 ; function test
550 ; -----------------------------------------
551 _test:
552 push ar2
553 push ar3
554 push ar4
555 push ar5
556 mov r2,dph
557 mov a,dpl
558 mov dptr,#_test_a_1_1
559 movx @dptr,a
560 inc dptr
561 mov a,r2
562 movx @dptr,a
563 ; ../source/main.c:62: return a + b;
564 mov dptr,#_test_PARM_2
565 movx a,@dptr
566 mov r2,a
567 inc dptr
568 movx a,@dptr
569 mov r3,a
570 mov dptr,#_test_a_1_1
571 movx a,@dptr
572 mov r4,a
573 inc dptr
574 movx a,@dptr
575 mov r5,a
576 mov a,r2
577 add a,r4
578 mov r2,a
579 mov a,r3
580 addc a,r5
581 mov r3,a
582 mov dpl,r2
583 mov dph,r3
584 pop ar5
585 pop ar4
586 pop ar3
587 pop ar2
588 ret
呼叫方式也是直接把傳遞值直接存入全域變數 _test_PARM_2 裏。
607 ; ../source/main.c:74: test(1, 2);
608 mov dptr,#_test_PARM_2
609 mov a,#0x02
610 movx @dptr,a
611 clr a
612 inc dptr
613 movx @dptr,a
614 mov dptr,#0x0001
615 lcall _test
SDCC 把局部變數被放到全域變數裏,相當於被宣告成靜態變數一樣,只是差在沒宣告 static 而已。由於此種原因,造成此類函數變成不可當成遞迴函數來使用,也就是不能自我呼叫函數。而且在 interrupt function 裏不可以呼叫此類函數,因為當中斷發生在此類函數時,interrupt function 又呼叫一次原來函數,會造成局部變數被修改,而造成不可預期的錯誤。所以需要在函數宣告後加上 __reentrant,讓編譯器按照一般 C 語言方式把局部變數都放到堆疊裏。下面的函數就是如此,把局部變數放到堆疊裏(sp-4)。
536 ;------------------------------------------------------------
537 ;Allocation info for local variables in function 'test'
538 ;------------------------------------------------------------
539 ;b Allocated to stack - offset -4
540 ;a Allocated to registers r2 r3
541 ;------------------------------------------------------------
542 ; ../source/main.c:60: int test(int a, int b) __reentrant
543 ; -----------------------------------------
544 ; function test
545 ; -----------------------------------------
546 _test:
547 push ar2
548 push ar3
549 push ar0
550 push ar1
551 push _bp
552 mov _bp,sp
553 mov r2,dpl
554 mov r3,dph
555 ; ../source/main.c:62: return a + b;
556 mov a,_bp
557 add a,#0xf8
558 mov r0,a
559 mov a,@r0
560 add a,r2
561 mov r2,a
562 inc r0
563 mov a,@r0
564 addc a,r3
565 mov r3,a
566 mov dpl,r2
567 mov dph,r3
568 pop _bp
569 pop ar1
570 pop ar0
571 pop ar3
572 pop ar2
573 ret
呼叫時,直接把傳遞值放入堆疊裏,而且在呼叫結束也會還原堆疊指針。
592 ; ../source/main.c:74: test(1, 2);
593 mov a,#0x02
594 push acc
595 clr a
596 push acc
597 mov dptr,#0x0001
598 lcall _test
599 dec sp
600 dec sp
各位有沒有在 reentrant function 裏發現一個不名的變數 _bp,用來做堆疊指標的運算,以取得參數的值。而這個 _bp 變數是宣告在 SDCC\lib\src\_bp.c --> __data unsigned char bp;。而且這個變數還被 printf_tiny 及 printf_fast 所使用,當然這二個函數有宣告成 __reentrant 函數,使用 _bp 變數也只是變相利用而已啦。
張貼留言