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