結(jié)構(gòu)(或C語言中的“結(jié)構(gòu)”)允許你將幾個(gè)相關(guān)的變量分組,并將它們作為一個(gè)單元來處理。它們是一種通過引入用戶定義類型來擴(kuò)展C語言類型系統(tǒng)的機(jī)制。在嵌入式開發(fā)的嵌入式系統(tǒng)中,結(jié)構(gòu)可以提供一種優(yōu)雅、直觀、高效的訪問硬件寄存器的方式,結(jié)構(gòu)的后一個(gè)屬性是Cortex微控制器軟件接口標(biāo)準(zhǔn)(CMSIS) [3]的基礎(chǔ)。
去typedef,還是不去typedef?
在C中聲明結(jié)構(gòu)(以及聯(lián)合和枚舉)的方式非常獨(dú)特,因?yàn)殛P(guān)鍵字“struct”后面的標(biāo)識符是一個(gè)標(biāo)記(例如,struct Foo{…};)。有趣的是,標(biāo)記(例如,Foo)占用的命名空間與變量、函數(shù)和類型不同。此類標(biāo)記不能單獨(dú)使用,但必須始終以關(guān)鍵字“struct”開頭,以形成詳細(xì)的類型說明符。這些隱藏標(biāo)記名的規(guī)則可能是C語言設(shè)計(jì)中的一個(gè)錯(cuò)誤,我建議遵循Dan Saks的指導(dǎo)原則將標(biāo)記轉(zhuǎn)換為typedef[1]。對于簡單(非自引用)結(jié)構(gòu),聲明根本不需要使用標(biāo)記:
對于自引用結(jié)構(gòu)(如鏈接列表或樹),標(biāo)記是必需的,但標(biāo)記名有意保持與typedefd名稱相同。
兩種推薦的結(jié)構(gòu)聲明都符合MISRA-C:2012指南,特別是指令2.4[2]。
內(nèi)存中的結(jié)構(gòu)布局
C編譯器將始終遵循結(jié)構(gòu)成員的順序,正如你在結(jié)構(gòu)中聲明的那樣。此外,第一個(gè)結(jié)構(gòu)成員始終與內(nèi)存中結(jié)構(gòu)的開頭對齊。(這對于模擬C語言中的繼承非常有用。)
但是,編譯器可以在每個(gè)成員(包括最后一個(gè)成員)之后插入額外的填充字節(jié)。編譯器添加填充以實(shí)現(xiàn)結(jié)構(gòu)成員的最佳對齊,從而實(shí)現(xiàn)更高效的訪問。因此,插入的填充字節(jié)數(shù)取決于CPU及其特定的對齊規(guī)則,這意味著結(jié)構(gòu)布局通常不可移植。
在嵌入式開發(fā)中,嵌入式編譯器通常提供非標(biāo)準(zhǔn)C語言擴(kuò)展,以允許你避免在結(jié)構(gòu)中填充。例如,視頻中的IAR編譯器提供了可應(yīng)用于結(jié)構(gòu)的擴(kuò)展關(guān)鍵字“__packed”。然而,“打包”結(jié)構(gòu)可能需要更多的CPU指令來訪問未對齊的成員,如切換到Cortex-M0 CPU后的視頻所示。
最小化填充對內(nèi)存的浪費(fèi)的一個(gè)很好的經(jīng)驗(yàn)法則是從最大到最小聲明結(jié)構(gòu)成員。
CMSIS中的結(jié)構(gòu)
C中的結(jié)構(gòu)定義了特定的內(nèi)存布局,可以映射到相關(guān)硬件寄存器的塊。這允許你方便地訪問硬件塊中的寄存器作為結(jié)構(gòu)成員,將所有地址計(jì)算(從結(jié)構(gòu)開始的偏移量)留給編譯器。此外,這種訪問硬件的方式可以利用現(xiàn)代CPU的特殊尋址模式(基于寄存器的立即偏移),這可能比硬編碼單個(gè)硬件地址更有效。
這一思想是Cortex微控制器軟件接口標(biāo)準(zhǔn)(CMSIS)[3]的基礎(chǔ),該標(biāo)準(zhǔn)將硬件接口定義為映射到各個(gè)硬件塊的C結(jié)構(gòu)。
結(jié)束注釋
“計(jì)算機(jī)科學(xué)中的所有問題都可以通過另一層間接解決”。在嵌入式開發(fā)中,但過多的間接性很快就會適得其反,考慮到這一點(diǎn),CMSIS在提供正確的抽象級別方面取得了正確的平衡,以便結(jié)構(gòu)成員名稱可以直接對應(yīng)于數(shù)據(jù)表中出現(xiàn)的寄存器。