全域變量簡直就是嵌入式系統的戈蘭高地。沖突最激烈的雙方是1. 做控制的工程師 2. 做非嵌入式的軟件工程師。
第一派做控制的工程師。他們普遍的理解就是「變量都寫成全域該有多方便」。我之前面試過一個非常有名的做控制實驗室裏出來的PhD/Master,前前後後陸續有快十個人。面試問題是用C寫PID。到後面的幾位面試的時候我都覺得沒有看的意義了,因為全都寫的是同一個風格。大概就是這樣的:
float
SetSpeed
;
float
err
;
float
err_last
;
float
Kp
,
Ki
,
Kd
;
float
integral
;
float
result
;
float
PID
(
float
speed
)
{
err
=
SetSpeed
-
speed
;
integral
+=
err
;
result
=
Kp
*
err
+
Ki
*
integral
+
Kd
*
(
err
-
err_last
);
err_last
=
err
;
return
result
;
}
程式碼的特點就是所有的變量一定定義在函數外面。問他們為什麽,回答是「全域變量方便偵錯」。
事實上在學校裏做搞自動控制的人最重要的根本就是控制的結果,而不是程式碼本身。程式碼只要能工作就行。變量名汙染,低耦合之類的和他們就不在同一個世界。進了公司有些人程式碼質素會變好,但有的還是會延續之前的習慣。前公司程式碼居里面凡是看不懂的程式碼一律都是那一兩個Control Engineer寫的,寫完了還會用自己的名字給函數命名的那種。
另外一派是之前不做嵌入式後來轉行的軟件工程師。程式碼的特點就是所有的靜態變量都不可以定義在.h檔裏,必須寫在.c檔裏以確保別的檔沒法存取它們。
別的檔真要存取怎麽辦?那就給每一個變量寫get/set函數啊!問題是靜態變量寫在.c檔裏編譯器是沒法最佳化get/set的。結果就大面積的變量存取要花幾倍的CPU時間去做get/set的函數呼叫。嵌入式專案很多情況下對硬件的壓榨是很極端的,CPU利用率90%都不算什麽,頂到97%都是有的。(註意下這些專案是即時性要求很高的,晚一個毫秒算不完都不行。不是跑在電腦上滑鼠卡一卡也無所謂的。)然後為了封裝性,在程式碼裏面塞這麽多get/set嗎?
總的來說嵌入式軟件裏大部份的程式碼都是中斷驅動的,天生就有很多變量是沒法使用參數傳遞的。全域變量的存在是因為正義站在這邊。但是嵌入式軟件遠遠沒有特殊到不需要按照正常軟件工程方法去管理的地步。要真有人認為「嵌入式軟件只要能工作就成,程式碼醜一點無所謂的」純粹是軟件工程水平不行,不是因為控制水平太高。
全域變量一定是要用的,管理它們也很重要。一些基本的程式碼規則:
- 如果只是檔內呼叫,全域變量只能寫在這個.c檔裏,不要寫進.h檔。
- 如果有檔外呼叫,全域變量要寫在.h檔裏。
- .h裏面的全域變量全域可讀,但是只有本檔組可以寫。別的檔要寫請呼叫set函數。
- 所有的全域變量無論在.h還是.c裏面都要包成同名struct。哪怕只有一個變量也要寫進struct裏面。比如PID.c裏面有一個pid_S,PID.h裏面有個PID_S。這樣其他人不僅可以立即辨識出一個變量是project內global/檔內static/函數內local,同時還能輕松追溯到這個函數是屬於哪個檔的。
- 不要寫函數內的static變量。函數內的static變量在實際的專案中幾乎就是bug生成器,沒法簡單的reset。而且對unit test非常不友好。