在嵌入式開發(fā)中,你查看過堆棧內(nèi)存,觀察過當(dāng)一個(gè)函數(shù)被調(diào)用并且稍后返回時(shí)到底發(fā)生了什么嗎?
C語言中的函數(shù)(以及其他編程語言中的過程、子例程或子程序)是計(jì)算機(jī)科學(xué)中最偉大的發(fā)明,它使程序比任何編程語言的任何其他特性都更容易理解。
函數(shù)保持代碼DRY
創(chuàng)建函數(shù)的一個(gè)明顯原因是避免重復(fù)代碼,也稱為DRY(不要重復(fù)自己)原則。你可以通過分離一段代碼并提供一個(gè)接口來實(shí)現(xiàn)這一點(diǎn),該接口允許你從程序中的不同位置輸入(調(diào)用)代碼。當(dāng)然,你需要編程語言支持來建立這樣的接口以及從函數(shù)調(diào)用和返回的機(jī)制。
堆棧的關(guān)鍵作用
對函數(shù)和返回的簡單調(diào)用似乎足夠簡單。返回地址可以存儲在寄存器中,例如ARM Cortex-M中的LR(鏈接寄存器)。但是在嵌入式開發(fā)中當(dāng)被調(diào)用的函數(shù)調(diào)用另一個(gè)函數(shù)時(shí),事情就變得復(fù)雜了。一個(gè)LR寄存器不能“記住”兩個(gè)返回地址。解決方案是將所有這樣的嵌套返回地址存儲在內(nèi)存中——存儲在一個(gè)稱為堆棧的數(shù)據(jù)結(jié)構(gòu)中。堆棧可以保存返回地址和函數(shù)內(nèi)部使用的局部變量。
函數(shù)調(diào)用堆棧的一個(gè)很好的比喻是一堆菜,你可以在堆棧的當(dāng)前頂部添加或刪除菜。為了支持這樣的數(shù)據(jù)結(jié)構(gòu),CPU只需要記住堆棧的當(dāng)前頂部,在ARM Cortex-M中,這是SP(堆棧指針)寄存器的工作。
開銷函數(shù)
ARM Cortex-M處理器上的開銷函數(shù)銷非常低。調(diào)用本身是一條BL指令,而返回是一條BX LR指令。除此之外,還要準(zhǔn)備函數(shù)參數(shù),這些參數(shù)在寄存器R0-R3中傳遞。
結(jié)束注釋
函數(shù)非常重要,不僅是為了避免重復(fù),而且是降低復(fù)雜性的主要機(jī)制,因?yàn)槟憧梢躁P(guān)注正在做的事情,而不是如何做。此外,在嵌入式開發(fā)中,在底層理解函數(shù)調(diào)用/返回機(jī)制和堆棧是通向其他關(guān)鍵概念的途徑,如中斷、RTOS(實(shí)時(shí)操作系統(tǒng))中的上下文切換和單獨(dú)編譯。