IAR Systems Anders Holmberg
姜橋 胡文婷 譯
本文關注的是使用C語言對安全功能有高度要求的安全關鍵系統(Safety-Critical System)進行開發的過程中涉及到的一些問題。盡管這種語言充滿不確定行為、依賴硬件以及其他陷阱,但它仍然是在安全關鍵開發領域使用的最為廣泛和流行的語言。而我們可以通過事先考慮和規劃,把這些潛在的問題變成了優勢。
早在1991年,《開發者的洞察力》雜志發表了一篇標題為《如何搬起石頭砸自己的腳》的文章,其開頭說:“隨著現代編程語言的大量擴散(彼此之間借鑒了大量類似的功能特點),有時您要弄清楚目前正在使用的是哪種編程語言都是很困難的。本指南所提供的內容可視作一項公共服務,以幫助程序員在困境中發現自我。”
由C開始的語言列表和其簡單的狀態:
◆C——搬起石頭砸了自己的腳
這個判斷可能顯得有點苛刻,但還是有些道理的。然而,即使替代編程語言可能沒有如類型安全和不確定的行為等方面的問題,但它們往往缺乏編程控制硬件功能。如果堅持用C語言,我們就需要在其優點以及明顯或不那么明顯的缺陷之間找一個平衡點。我們可以從兩個不同的角度觀察C語言在安全關鍵功能編程開發中的應用:
◆在安全關鍵的項目中有哪些外部需求,怎樣選擇編程語言?
◆可以做些什么來彌補一些明顯的C語言的缺陷呢?特別是針對現有的代碼。
如果您的產品涉及到如汽車、工業控制、醫療設備、鐵路等領域,這些產品一般來說都有正式的功能安全要求。這種要求可以歸結為一個非常具體的產品容錯率要求,或產品中某些特定功能的允許故障發生的概率。它也可以是按照IEC61508(電氣和電子可編程器件)、ISO26262(汽車),或EN50126x(鐵路)等通用功能安全標準開發的產品。至少10年以來,一個明顯的趨勢是安全功能的實施正在離開純機械或PLC自動化控制,從而進入單片機的世界,因此這種要求也延伸到了軟件領域。
由于各種標準的軟件要求的意圖是類似的,我們使用IEC61508標準作為一個例子。該標準規定了許多特定行業標準的基礎要求,符合IEC61508標準的產品很大程度上也是符合ISO26262標準的。
這些標準將嚴重影響你的工作方式和工作文檔:從收集需求,到計劃你的產品如何在客戶現場部署和退役。你和你的項目利益相關方并不能單方面決定項目是否已經成功達成所選標準的規定,還必須說服來自認證機構的第三方評估員,或者是在組織中扮演類似角色的人員,獲得他們的認可。
這些標準大部分使用的是安全完整性等級概念的變種。所以根據產品的分類,在具體運用合適的標準時將會有一些變化。
先來說一個有趣的問題:這與我所選擇的編程語言有什么關系呢?關系可大了,接下來就開始說明這個問題。表1給出了如何根據應用程序或者安全功能所需要達到的安全完整性水平來選擇一個合適的編程語言的相關建議。

表1 軟件設計和開發—支持工具與編程語言
HR是Highly Recommended的縮寫,這意味著對于標有HR標志的項目,應當盡量遵守該建議,如果不能遵守,也請給出一個100%合理的理由。
正如在表1中看到的,使用合適的編程語言是被強烈推薦的,但是究竟為何要被推薦,我們并沒有多大感覺,對嗎?不過,表中引用的C附錄給出了一個關于合適的編程語言的定義:語言的定義應該是充分且明確的。語言應該是面向用戶或問題的,而不是面向處理器/平臺的。與專用語言相比,應當優先選擇廣泛使用的語言或它們的子集。語言應鼓勵使用小且方便管理的軟件模塊;應當限制對在特定的軟件模塊中的數據、變量子范圍定義,以及任何其他類型錯誤限制結構的訪問。
讓我們看看C語言是否均能滿足上述各個部分的定義:
① 語言應充分明確定義:這取決于你如何考慮,可以說C99包含至少190未定義行為。
② 語言應該是面向用戶或問題的,而不是面向處理器/平臺的:最初創建C語言是為了成為PDP-11架構的系統開發語言;一個特定目標的C語言實現必然與另一個目標的實現是不同的,有時對相同目標的實現甚至都是不同的,我們真的很難認為C符合這部分定義……
③ 與專用語言相比,應當優先選擇廣泛使用的語言或它們的子集:終于,我們發現C語言是能夠滿足這條定義的!
④ 語言應該鼓勵使用小且方便管理的軟件模塊,限制對在特定的軟件模塊中數據、變量子范圍定義,以及任何其他類型錯誤限制結構的訪問:雖然C沒有明確禁止創建符合這些條件的抽象概念,但是坦率地說,我們認為正好相反,C語言本身絕對沒有對此類概念提供任何支持。
C語言并不完全符合這些標準的期望。然而,我們可以做些什么呢?其實,答案很簡單,至少我們在讀這些標準。如果繼續讀下去,會發現一個特定語言的判斷表,以下是C語言相關描述:

9 C R-NR NR 10 C及其子集和代碼編寫標準,以及靜態分析工具的使用HR HR HR HR
盡管并沒有推薦使用C語言,但是,如果嚴格遵守代碼編寫標準并使用靜態分析工具,帶有一個適合子集的C語言是被強烈推薦的。但是,上文中提及的子集和代碼編寫標準怎么理解?
在此背景下,語言子集就是為了減少編程錯誤的概率,增加找到已經悄然潛伏在代碼庫中錯誤的可能性。對于C語言,這意味著盡可能杜絕使用未定義的行為,實現定義明確的行為。有很多這樣的語言子集可供使用,其中最廣為人知的可能是 MISRA-C。MISRA-C規則集開始是英國汽車工業軟件可靠性協會的一項倡議,僅僅著眼于汽車軟件。多年來,MISRA-C規則已經傳播到世界各地,并擴展到其他細分行業,其規則集是現在嵌入式行業使用最廣泛的C子集。
IEC61508中也涉及了很多代碼編寫標準的說明。下面是一些MISRA-C規則之外我們需要考慮的問題:
◆如何來保護共享資源,如全局變量的訪問。
◆使用為對象分配的棧和堆的內存。
◆允許或不允許遞歸?
◆復雜性的限制,如限制函數的循環復雜度。
◆在某些不合適的情況下放棄遵循MISRA-C規則。
◆如何使用編譯器的特定功能,如本征函數或語言擴展。
◆如何使用范圍檢查,斷言和前置/后置條件以及類似的結構來捕獲錯誤。
◆模塊之間的接口組織和訪問。
◆文檔要求。
從本質上講,一種代碼編寫標準應提供一些建議來指導如何處理影響到代碼質量和完整性的問題,而不是由語言和其子集提供明確的建議。
總結出以下幾個重點:
◆開始寫代碼之前,先熟悉相關標準定義的軟件開發要求;
◆使用MISRA-C作為代碼編寫標準基礎;
◆檢查volatile關鍵字使用;
◆實施一個堆棧分配測試和分析策略。
本文為期刊縮略版,全文見本刊網站www.mesnet.com.cn。