圖1 汽車事故原因分布圖 MISRAC:2004認(rèn)為C程序設(shè)計(jì)中存在的風(fēng)險(xiǎn)可能由5個(gè)方面造成:程序員的失誤、程序員對(duì)語(yǔ)言的誤解、程序員對(duì)編譯器的誤解、編譯器的錯(cuò)誤和運(yùn)行出錯(cuò)(runtime errors)。
程序員的失誤是司空見(jiàn)慣的。程序員是人,難免會(huì)犯錯(cuò)誤。很多由程序員犯下的錯(cuò)誤可以被編譯器及時(shí)地糾正(如鍵入錯(cuò)誤的變量名等),但也有很多會(huì)逃過(guò)編譯器的檢查。相信任何一個(gè)程序員都曾經(jīng)犯過(guò)將“= =”誤寫成“=”的錯(cuò)誤,編譯器可能不會(huì)認(rèn)為
if(x=y)
是一個(gè)程序員的失誤。
再舉個(gè)例子,大家都知道++運(yùn)算符。假如有下面的指令:
i=3;
printf(“%d”,++i);
輸出應(yīng)該是多少?如果是:
printf(“%d”,i++);
呢?如果改成-i++呢?i+++i呢?i+++++i呢?絕大多數(shù)程序員恐怕已經(jīng)糊涂了。在MISRAC:2004中,會(huì)明確指出++或--運(yùn)算符不得和其他運(yùn)算符混合使用。
C語(yǔ)言非常靈活,它給了程序員非常大的自由。但事情有好有壞,自由越大,犯錯(cuò)誤的機(jī)會(huì)也就越多。
如果說(shuō)有些錯(cuò)誤是程序員無(wú)心之失的話,那么因?yàn)槌绦騿T對(duì)C語(yǔ)言本身或是編譯器特性的誤解而造成的錯(cuò)誤就是“明”知故犯了。C語(yǔ)言有一些概念很難掌握,非常容易造成誤解,如表達(dá)式的計(jì)算。請(qǐng)看下面這條語(yǔ)句:
if ( ishigh && (x == i++))
很多程序員認(rèn)為執(zhí)行了這條指令后,i變量的值就會(huì)自動(dòng)加1。但真正的情況如何呢?MISRA中有一條規(guī)則:邏輯運(yùn)算符&&或 的右操作數(shù)不得帶有副作用(side effect)*,就是為了避免這種情況下可能出現(xiàn)的問(wèn)題。
*所謂帶有副作用,就是指執(zhí)行某條語(yǔ)句時(shí)會(huì)改變運(yùn)行環(huán)境,如執(zhí)行x=i++之后,i的值會(huì)發(fā)生變化。
另外,不同編譯器對(duì)同一語(yǔ)句的處理可能是不一樣的。例如整型變量的長(zhǎng)度,不同編譯器的規(guī)定就不同。這就要求程序員不僅要清楚C語(yǔ)言本身的特性,還要了解所用的編譯器,難度很大。
還有些錯(cuò)誤是由編譯器(或者說(shuō)是編寫編譯器的程序員)本身造成的。這些錯(cuò)誤往往較難發(fā)現(xiàn),有可能會(huì)一直存留在最后的程序中。
運(yùn)行錯(cuò)誤指的是那些在運(yùn)行時(shí)出現(xiàn)的錯(cuò)誤,如除數(shù)等于零、指針地址無(wú)效等問(wèn)題。運(yùn)行錯(cuò)誤在語(yǔ)法檢查時(shí)一般無(wú)法發(fā)現(xiàn),但一旦發(fā)生很可能導(dǎo)致系統(tǒng)崩潰。例如:
#define NULL 0
……
char* p;
p=NULL;
printf(“Location of 0 is %d\n”, *p);
語(yǔ)法上沒(méi)有任何問(wèn)題,但在某些系統(tǒng)上卻可能運(yùn)行出錯(cuò)。
C語(yǔ)言可以產(chǎn)生非常緊湊、高效的代碼,一個(gè)原因就是C語(yǔ)言提供的運(yùn)行錯(cuò)誤檢查功能很少,雖然運(yùn)行效率得以提高,但也降低了系統(tǒng)的安全性。
有句話說(shuō)得好,“正確的觀念重于一切”。MISRAC規(guī)范對(duì)于嵌入式程序員來(lái)講,一個(gè)很重要的意義就是提供給他們一些建議,讓他們逐漸樹(shù)立一些好的編程習(xí)慣和編程思路,慢慢摒棄那些可能存在風(fēng)險(xiǎn)的編程行為,編寫出更為安全、健壯的代碼。比如,很多嵌入式程序員都會(huì)忽略注釋的重要性,但這樣的做法會(huì)降低程序的可讀性,也會(huì)給將來(lái)的維護(hù)和移植帶來(lái)風(fēng)險(xiǎn)。嵌入式程序員經(jīng)常要接觸到各種的編譯器,而很多C程序在不同編譯器下的處理是不一樣的。MISRAC:2004有一條強(qiáng)制規(guī)則,要求程序員把所有和編譯器特性相關(guān)的C語(yǔ)言行為記錄下來(lái)。這樣在程序員做移植工作時(shí),風(fēng)險(xiǎn)就降低了。
3 MISRAC的負(fù)面效應(yīng)
程序員可能會(huì)擔(dān)心采用MISRAC:2004規(guī)范會(huì)對(duì)他們的程序有負(fù)面影響,比如可能會(huì)影響代碼量、執(zhí)行效率和程序可讀性等。應(yīng)該說(shuō),這種擔(dān)心不無(wú)道理。縱觀141條MISRAC:2004編程規(guī)范,大多數(shù)的規(guī)則并不會(huì)對(duì)程序的代碼量、執(zhí)行效率和可讀性造成什么大的影響;一部分規(guī)則可能會(huì)以增加存儲(chǔ)器的占用空間為代價(jià)來(lái)增加執(zhí)行效率,或者增加代碼的可讀性;但是,也確實(shí)存在著一些規(guī)則可能會(huì)降低程序的執(zhí)行效率。
一個(gè)典型的例子就是關(guān)于聯(lián)合體的使用。MISRAC:2004有一條規(guī)則明確指出:不得使用聯(lián)合體。這是因?yàn)?在聯(lián)合體的存儲(chǔ)方式(如位填充、對(duì)齊方式、位順序等)上,各種編譯器的處理可能不同。比如,經(jīng)常會(huì)有程序員這樣做:一邊將采集得到的數(shù)據(jù)按照某種類型存入一個(gè)聯(lián)合體,而同時(shí)又采用另外一種數(shù)據(jù)類型將該數(shù)據(jù)讀出。如下面這段程序:
typedef union{
uint32_t word;
uint8_t bytes[4];
}word_msg_t;
unit32_t read_word_big_endian (void) {
word_msg_t tmp;
tmp.bytes[0] = read_byte();
tmp.bytes[1] = read_byte();
tmp.bytes[2] = read_byte();
tmp.bytes[3] = read_byte();
return (tmp.word);
}
原理上,這種聯(lián)合體很像是一個(gè)硬件上的雙口RAM存儲(chǔ)器。但程序員必須清楚,這種做法是有風(fēng)險(xiǎn)的。MISRAC:2004推薦用下面這種方法來(lái)做:
uint32_t read_word_big_endian (void) {
uint32_t word;
word=((unit32_t)read_byte())<<24;
word=word (((unit32_t)read_byte())<<16);
word=word (((unit32_t)read_byte())<<8);
word=word ((unit32_t)read_byte());
return(word);
}
先不論為什么這樣做會(huì)更安全,只談執(zhí)行效率,這種采用二進(jìn)制數(shù)移位的方法遠(yuǎn)遠(yuǎn)不如使用聯(lián)合體。到底是使用更安全的做法,還是采用效率更高的做法,需要程序員權(quán)衡。對(duì)于一些要求執(zhí)行效率很高的系統(tǒng),使用聯(lián)合體仍然是可以接受的方法。當(dāng)然,這是建立在程序員充分了解所用編譯器的基礎(chǔ)上的,而且程序員必須對(duì)這種做法配有相應(yīng)的注釋。
4 發(fā)展中的MISRAC
MISRAC并非完美,它自身的發(fā)展也印證了這一點(diǎn)。MISRAC:2004就去掉了MISRAC:1998中的15條規(guī)則。今后的發(fā)展,MISRAC仍然要解決很多問(wèn)題。比如,MISRAC:2004是基于C90標(biāo)準(zhǔn)的,但最新的國(guó)際C標(biāo)準(zhǔn)是C99,而C99中沒(méi)有確切定義的C語(yǔ)言特性幾乎比C90多了一倍,MISRAC如何適應(yīng)新的標(biāo)準(zhǔn)還需要進(jìn)一步探討。
另外,C++在嵌入式應(yīng)用中也越來(lái)越受到重視,MISRA正在著手制定MISRAC++編程規(guī)范。讀者可以通過(guò)訪問(wèn)網(wǎng)站http://www.misra.org.uk了解MISRAC的發(fā)展動(dòng)向。
5 對(duì)MISRAC的思考
嵌入式系統(tǒng)并不算是一個(gè)獨(dú)立的學(xué)科,但作為一個(gè)發(fā)展中的行業(yè),它確實(shí)需要有一些自己的創(chuàng)新之處。嵌入式工程師們不應(yīng)僅僅局限于從計(jì)算機(jī)專家那里學(xué)習(xí)相關(guān)理論知識(shí),并運(yùn)用于自己的項(xiàng)目,還應(yīng)該共同努力去完善自己行業(yè)的標(biāo)準(zhǔn)和規(guī)范,為嵌入式系統(tǒng)的發(fā)展做出貢獻(xiàn)。MISRAC編程規(guī)范就是一個(gè)很好的典范。它始于汽車工程師和軟件工程師經(jīng)驗(yàn)的總結(jié),然后逐漸發(fā)展成為一種對(duì)整個(gè)嵌入式行業(yè)都有指導(dǎo)意義的規(guī)范。對(duì)于推動(dòng)整個(gè)嵌入式行業(yè)的正規(guī)化發(fā)展,MISRAC無(wú)疑有著重要意義。
從另一個(gè)角度講,MISRAC規(guī)范也可以看成是嵌入式工程師對(duì)軟件業(yè)的一種完善。嵌入式工程師雖然不是計(jì)算機(jī)專家,但卻對(duì)嵌入式應(yīng)用有著最深刻的了解,將自己在嵌入式應(yīng)用中的經(jīng)驗(yàn)和體會(huì)貢獻(xiàn)給其他行業(yè),也是他們應(yīng)該肩負(fù)的責(zé)任。
參考文獻(xiàn)
1 MISRAC:2004, Guidelines for the use of the C language in critical systems. The Motor Industry Software Reliability Association, 2004
2 Harbison III. Samuel P, Steele Jr. Guy L. C語(yǔ)言參考手冊(cè). 邱仲潘,等譯. 第5版. 北京:機(jī)械工業(yè)出版社,2003
3 Kernighan. Brian W, Ritchie. Dennis M. C程序設(shè)計(jì)語(yǔ)言. 徐寶文,等譯. 第2版. 北京:機(jī)械工業(yè)出版社,2001
4 Koenig Andrew. C陷阱與缺陷. 高巍譯. 北京:人民郵電出版社,2002
5 McCall Gavin. Introduction to MISRAC:2004, Visteon UK, http://www.MISRAC2.com/
6 Hennell Mike. MISRA CIts role in the bigger picture of critical software development, LDRA. http://www.MISRAC2.com/
7 Hatton Les. The MISRA C Compliance Suite—The next step, Oakwood Computing. http://www.MISRAC2.com/
8 Montgomery Steve. The role of MISRA C in developing automotive software, Ricardo Tarragon. http://www.MISRAC2.com/





