有關C的固件樣式問題。
我正在整理一些舊代碼。討厭的功能之一是全局變量分散在源文件中。我可以讓他們中的一些當地人,這很好。但是,其餘如何處理。我更喜歡創建一個結構並將其放入其中。
我知道從理論上講,訪問它們時還存在另一個間接級別,但是從POV樣式來看,這比將它們放入文件globals.c和/或globals.h更好或更糟?
有關C的固件樣式問題。
我正在整理一些舊代碼。討厭的功能之一是全局變量分散在源文件中。我可以讓他們中的一些當地人,這很好。但是,其餘如何處理。我更喜歡創建一個結構並將其放入其中。
我知道從理論上講,訪問它們時還存在另一個間接級別,但是從POV樣式來看,這比將它們放入文件globals.c和/或globals.h更好或更糟?
眾所周知的 不良做法 擁有一個諸如“ globals.h”或“ includes.h”之類的“超級標頭”文件,因為除了全局變量在首先,這還會在項目中的每個不相關文件之間創建緊密耦合的依賴關係。
假設您有一個PWM驅動器和一個RS-232調試打印模塊。您想獲取RS-232調試打印模塊,然後在另一個項目中重新使用它。突然,您發現自己需要一些PWM.h,而您對它的含義或需求來自何處卻一無所知。您會發現自己在問為什麼在地球上需要PWM驅動器才能運行RS-232。
然後,我們甚至都沒有考慮過重新加入這個凌亂的全局結構。甚至不這樣做。
解開全局意大利麵條的正確方法應該是這樣的:
static
來將其移動到本地 .c
文件範圍?您是否可以通過實現setter / getter函數來減少 .c
文件外部的訪問?如果上述所有操作均失敗,則您發現自己所查看的變量應該是寄存器映射的內存映射硬件寄存器部分,或者它是在維護期間添加的一些實時關鍵性臟修復程序。
將實例化為單個全局變量的結構定義為有效。以'the_global.the_var'形式進行的訪問不會增加運行時的開銷,並且可以說明它確實是全局的。 正如 https://stackoverflow.com/questions/2868651/includes-c-header-file-with-lots-of-global-variables所述,它使您免於單獨的聲明和定義。>
就我個人而言,我不會費心去做一個結構,而是喜歡將全局變量排序到頭文件中,在這些文件中我覺得它們在邏輯上屬於它們,並為每個頭文件使用通用前綴。示例:文件calculate.h聲明為“ extern int calc_result;”並定義了“ int calc_result;”
其他變量不再是文件本地文件,即“靜態int結果;”在.c文件中。
由於您擁有舊版代碼,我認為除了整理代碼之外,您將無法進行很多工作,因此我想,使結構清晰的最快解決方案是最好的。
如果您不能擺脫全局變量,我想說的是,如果它們確實相關,則只應將它們打包在一起。如果沒有,那麼我將它們分開或放在較小的結構中。
此外,我也不想要globals.h文件。將它們放在它們最所屬的源文件的頂部。這樣,在瀏覽代碼時,您可能會留在原本的地方或去可能要去的地方。
實際上,這並不好,唯一的好處是您“知道”哪些是全球性的。因此,所有全局變量都集中在一個位置,但是它們仍然分散在各處。
C
對於每個全局變量:
extern
關鍵字)。C ++
對於每個全局變量:
將全局變量放入結構的最大優點是:
更小的優勢是:
合理使用全局變量可以降低堆棧要求,但是如果沒有充分說明它們的用法並遵守規則,則會增加出錯的風險。
為什麼在 struct
中應該有另一種間接訪問變量的級別?如果您這樣聲明結構:
typedef struct tagUART {
int field1;
int field2;
} UART;
然後聲明一個全局變量:
UART uart;
而不是聲明這些全局變量:
int uartField1;
int uartField2;
與通過訪問 uartField1
。編譯器知道結構的起始地址,而struct聲明定義結構中的字段偏移量。編譯器知道參考點的一切。
現在,如果您開始將指向 uart
的指針作為函數參數傳遞給指針,並且函數調用不會被內聯,則將出現額外的間接級別。在那裡,編譯器失去了“嘿,這些字段都有一個眾所周知的地址”的事實。
我知道從理論上講,訪問它們時還存在另一種間接方式,
這取決於一個真正愚蠢的編譯器可能會為結構引入額外的間接級別,但是如果涉及到一個結構,智能編譯器實際上可以產生更好的代碼。
與其他答案相反,編譯器不知道全局變量的地址。對於同一編譯單元中的全局變量,它知道它們相對於彼此的位置,但不知道它們的絕對位置。對於其他編譯單元中的變量,無論是絕對值還是相對值,都不知道它們的位置。
相反,由編譯器生成的代碼將使用佔位符表示全局變量的位置,然後在確定最終地址時(通常是在鏈接程序時,但有時直到運行)將其替換為實際位置。 )。
此外,儘管有些CPU(如6502)可以在沒有任何預先設置的情況下直接訪問全局內存位置,但有許多不能。例如,在arm上,要訪問全局變量,編譯器通常必須首先使用文字池或在mov的最新版本中使用movw / movt將變量的地址加載到寄存器中。然後使用相對寄存器加載指令訪問全局變量。
這意味著對於編譯單元外部的變量,訪問同一全局結構的多個元素可能比訪問單個全局變量更有效。
要對此進行測試,我將以下代碼通過ARM gcc 8.2和-O3優化輸入到了godbolt中。
extern int a;
extern int b;
struct Foo {
詮釋
int b;
};
extern struct Foo foo;
無效f1(void){
a = 1;
b = 2;
}
無效f2(void){
foo.a = 1;
foo.b = 2;
}
這導致了
f1:
mov r0,#1
mov r2#2
ldr r1,.L3
ldr r3,.L3 + 4
str r0,[r1]
str r2,[r3]
bx lr
.L3:
.word a
.word b
f2:
mov r1#1
mov r2#2
ldr r3,.L6
stm r3,{r1,r2}
bx lr
.L6:
.word foo
在f1的情況下,我們看到編譯器從文字池中單獨加載a和b的地址(文字池中的地址稍後將由鏈接器填充)。
但是對於f2來說,編譯器只需從文字池中加載一次結構的地址,更好的是,因為它知道兩個變量在內存中彼此相鄰,因此可以使用單個“存儲”將它們寫入多個”指令,而不是兩個單獨的存儲指令。
但是從樣式POV來看,這比將它們放入文件globals.c和/或globals.h更好或更糟?
取決於變量是否實際相關的IMO。