嵌入式開發(fā)人員經(jīng)常抱怨沒有一種編程語言能完美滿足他們的特定需求。在某種程度上,這種情況并不令人驚訝,因為盡管有很多開發(fā)人員在開發(fā)嵌入式應(yīng)用程序,但他們?nèi)匀恢皇鞘澜缇幊躺鐓^(qū)的一小部分。然而,一些語言在開發(fā)時就考慮到了嵌入式。值得注意的例子是 PL/M、Forth 和 Ada,它們都已被廣泛使用,但從未被普遍接受。其他語言,如 Rust,正在獲得支持,但尚未成為主流。幾乎被普遍采用的折衷方案是 C。如何才能使這種折衷方案最有效地發(fā)揮作用?
C 語言簡潔、富有表現(xiàn)力且功能強大。它為程序員提供了編寫高效、可讀和可維護的代碼的方法。所有這些功能都說明了它的受歡迎程度。不幸的是,該語言還使粗心的開發(fā)人員能夠編寫危險的、不安全的代碼,這些代碼可能會在開發(fā)項目的所有階段和部署中導(dǎo)致嚴重的問題。對于安全性和/或安全性是主要優(yōu)先事項的應(yīng)用程序,語言的這些缺點是一個主要問題。
正是在這種背景下,在 1990 年代后期,汽車工業(yè)軟件可靠性協(xié)會 (MISRA) 推出了一套在車輛系統(tǒng)中使用 C 的指南,即后來的 MISRA C。從那時起,該指南一直在穩(wěn)步推進。完善,不時發(fā)布更新。還建立了使用 C++ 的類似方法。盡管該指南最初針對的是汽車軟件開發(fā)人員,但很快就意識到它們同樣適用于安全至關(guān)重要的許多其他應(yīng)用領(lǐng)域,并且該標(biāo)準現(xiàn)在已被許多行業(yè)廣泛采用。
盡管 MISRA C 不是風(fēng)格指南——事實上,許多用戶在應(yīng)用風(fēng)格指南和標(biāo)準的同時——許多規(guī)則也促進了清晰、可讀、可維護的代碼的編寫。這是非常有益的,因為易于理解的代碼不太可能包含細微的錯誤或未定義的行為。
MISRA C 的完整詳細信息可從https://misra.org.uk獲得,并且有許多可用的工具支持該方法。
我將在這里簡單介紹一下指南。我的參考資料來自 MISRA C:2012 第三版,第一版。MISRA C 正在不斷審查中,增量更改解決了指南的清晰度和準確性以及對新版本 C 語言標(biāo)準的支持。盡管細節(jié)發(fā)生了變化,但整體理念和方法沒有變化。
規(guī)則 13.2 – 在所有允許的評估順序下,表達式的值及其持續(xù)的副作用應(yīng)相同
C 語言標(biāo)準在表達式中的求值順序方面為編譯器提供了非常廣泛的自由度。因此,任何對評估順序敏感的代碼都是依賴于編譯器和依賴于編譯器的代碼,因此應(yīng)始終將其視為不安全的。
例如,遞增和遞減運算符的使用可能會很麻煩:
val = n++ + arr[n];
訪問arr的哪個元素?程序員是否期望用于索引數(shù)組的n的值是在增量之前還是之后?盡管看起來好像在數(shù)組索引之前執(zhí)行了增量,但它假設(shè)了左右表達式評估,這不是一個有效的假設(shè)。所以,代碼不清楚,應(yīng)該重寫如下:
val = n + arr[n+1];
n++;
或者
val = n++;
val += arr[n];
甚至
val = n;
n++;
val += arr[n];
您選擇哪個選項取決于個人風(fēng)格。它們都執(zhí)行相同的操作,事實上,優(yōu)化編譯器很可能會生成完全相同的代碼。
一個表達式中使用的多個函數(shù)調(diào)用可能會出現(xiàn)類似的問題。函數(shù)調(diào)用可能具有影響另一個函數(shù)的副作用。例如:
val = fun1() + fun2();
在這種情況下,如果任何一個函數(shù)都可以影響另一個函數(shù)的結(jié)果,那么代碼就是模棱兩可的。要編寫安全代碼,必須消除任何可能的歧義:
val = fun1();
val += fun2();
現(xiàn)在很清楚fun1()是首先執(zhí)行的。
規(guī)則 17.2 – 函數(shù)不得直接或間接調(diào)用自己
有時,表達算法的一種優(yōu)雅方式是使用遞歸。但是,除非對遞歸進行非常嚴格的控制,否則存在堆棧溢出的危險,這反過來又會導(dǎo)致非常難以定位錯誤。在安全關(guān)鍵代碼中,應(yīng)避免遞歸。
Rule 19.2 – The union keyword should not be used
Although C is a typed language, typing is not very strictly enforced, and developers may be tempted to override typing to “simplify” their code. Adhering to the constraints of data types is essential to create safe code, as any attempts to get around data types can produce undefined results. The union keyword can be used for a number of purposes, which generally result in unclear code, but can also be a means to circumvent typing.
One example would be using a union to “take apart” an unsigned integer, thus:
union e
{
unsigned int ui;
unsigned char a[4];
}f;
在這種情況下, ui的每個字節(jié)都可以作為 a 的元素訪問。但是,我們不能確定a[0]是否是最不重要的字節(jié),因為這是一個實現(xiàn)問題。(本質(zhì)上與處理器的字節(jié)序有關(guān)。)替代方法可能是使用移位和屏蔽,因此:
unsigned char getbyte(unsigned int input, unsigned int index)
{
input >>= (index * 8);
返回輸入 & 0xff;
}
有人可能會爭辯說,這些規(guī)則(以及 MISRA C 的大多數(shù),如果不是全部的話)只是常識,任何優(yōu)秀的程序員都會采用這種方法。這可能是真的,但一套明確的指導(dǎo)方針讓機會更少。
審核編輯:湯梓紅
-
嵌入式
+關(guān)注
關(guān)注
5052文章
18908瀏覽量
300702 -
C語言
+關(guān)注
關(guān)注
180文章
7581瀏覽量
135541 -
MISRA
+關(guān)注
關(guān)注
0文章
21瀏覽量
6953
發(fā)布評論請先 登錄
相關(guān)推薦
評論