單片機 main 函數結束去哪兒了?

正常的程序,都不會跳出 main,但是,如果跳出了 main 函數,程序到底去哪兒了,你有相關這個問題嗎?

一、問題提出

今天在單片機 led 模塊定義函數中看到一個有趣的問題。提問者在進行基本的 C51 編程實驗,編寫了一個簡單的 C51 程序如下:

#include <REGX51.H>

void test(num) {
    switch(num) {
        case 1: P2_0=0; P2_1=0; 
            break;
    }
}

void main(void) {
    test(1);
}

程序執行完之後,可以看到實驗板上的有兩個 LED 被點亮,另外六個居然微微發亮。

如果在主程序中,增加一個無限循環:while(1); ,則電路板上的就不再會出現 “微微點亮” 的現象了。

#include <REGX51.H>

void test(num) {
    switch(num) {
        case 1: P2_0=0; P2_1=0; 
            break;
    }
}

void main(void) {
    test(1);
    while(1);
}

上面兩種情況的區別,在於第二個程序中主循環 main() 函數始終沒有退出,而第一個程序,main() 函數退出了。似乎前面 LED 微微點亮 應該與主函數退出之後,單片機都幹了些啥有關係。

那麼就剩下一個問題:對於普通的嵌入式系統,C 語言編程中 main() 函數退出之後,程序去哪兒了

二、程序去哪兒了?

從上面提問者書寫的代碼來看,應該是一位 C51 的愛好者,使用的是 C51 的編譯器,在一款 C51 開發板上愉快的進行實驗。他一開始沒有安裝嵌入式程序開發的慣例 在主程序 void main(void) 中利用無限循環將程序控制在主程序函數中,就出現了前面實驗結果中令人迷惑的情況。

注:他是一個膽大心細的人,觀察還挺仔細的。

2.1 盤古開天闢地

對於 C 語言編程來說,所有的用戶程序世界是從主程序 main() 開始的。給用戶程序開天闢地的任務是由一小段盤古代碼 STARTUP.A51。

51 單片機程序執行流程(STARTUP.A51 管理 Main 函數的執行)

下面截取了 STARTUP.A51 代碼的一段,可以看到盤古在單片機 RESET 之後做了點準備工作(初始化全局變量、堆棧指針)之後,就直接跳轉至:?C_START

 NAME    ?C_STARTUP

?C_C51STARTUP   SEGMENT   CODE
?STACK          SEGMENT   IDATA

                RSEG    ?STACK
                DS      1

                EXTRN CODE (?C_START)
                PUBLIC  ?C_STARTUP

                CSEG    AT      0
?C_STARTUP:     LJMP    STARTUP1

                RSEG    ?C_C51STARTUP

STARTUP1:

IF IDATALEN <> 0
                MOV     R0,#IDATALEN - 1
                CLR     A
IDATALOOP:      MOV     @R0,A
                DJNZ    R0,IDATALOOP
ENDIF

IF XDATALEN <> 0
                MOV     DPTR,#XDATASTART
                MOV     R7,#LOW (XDATALEN)
  IF (LOW (XDATALEN)) <> 0
                MOV     R6,#(HIGH (XDATALEN)) +1
  ELSE
                MOV     R6,#HIGH (XDATALEN)
  ENDIF
                CLR     A
XDATALOOP:      MOVX    @DPTR,A
                INC     DPTR
                DJNZ    R7,XDATALOOP
                DJNZ    R6,XDATALOOP
ENDIF

IF PPAGEENABLE <> 0
                MOV     PPAGE_SFR,#PPAGE
ENDIF

IF PDATALEN <> 0
                MOV     R0,#LOW (PDATASTART)
                MOV     R7,#LOW (PDATALEN)
                CLR     A
PDATALOOP:      MOVX    @R0,A
                INC     R0
                DJNZ    R7,PDATALOOP
ENDIF

IF IBPSTACK <> 0
EXTRN DATA (?C_IBP)

                MOV     ?C_IBP,#LOW IBPSTACKTOP
ENDIF

IF XBPSTACK <> 0
EXTRN DATA (?C_XBP)

                MOV     ?C_XBP,#HIGH XBPSTACKTOP
                MOV     ?C_XBP+1,#LOW XBPSTACKTOP
ENDIF

IF PBPSTACK <> 0
EXTRN DATA (?C_PBP)
                MOV     ?C_PBP,#LOW PBPSTACKTOP
ENDIF

                MOV     SP,#?STACK-1
                LJMP    ?C_START

                END

上面的代碼也被博文 51 單片機程序執行流程(STARTUP.A51)中進行逐步調試跟蹤驗證過:

2.2 世界盡頭

由於進入 main() 函數是長跳轉,所以 main 函數是不會正常返回到啓動程序 STARTUP.A51,那麼程序去哪了?

在博文單片機 C 語言 while(1) 的問題中作者對於 KEIL 編譯器和 PIC 的 MAPLAB 編譯器對於 main 函數的最後時光進行了反彙編查看。

Keil 編譯器

在 main 函數的最後,程序增加了一下幾行代碼:

MOV R0, #0x7F
CLR A
MOV @R0, A
DJNZ R0, (3)
MOV SP, #0x0C
LJMP main

這幾條語句,前 4 條,是將我們單片機的內存的前 128 個地址清零,第 5 條,是定義堆棧,第 6 條,是將程序重新跳轉到 main 函數的首行進行執行。

MAPLAB 編譯器

PIC 單片機語言程序進行跟蹤,發現 main() 函數最後一條語句爲 reset,也就是單片機直接復位,這是 MAPLAB 編譯器根據 PIC 單片機特點增加的復位語句。

總結

對於嵌入式系統,如果沒有運行 RTOS,那麼程序開發中的主函數(main())需要通過某種機制使其永遠愉快的運行下去,它沒有終點。如果想從 main 函數中退出,具體幹什麼是由所使用的 C 語言編譯器決定的。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/o12kE0MVkiVFRZ6CXtL1Aw