代碼重用通常是新項目中的主要考慮因素,無論是在利用先前項目的遺留代碼方面,還是作為后續項目的基礎。靜態分析可用于確保遺留代碼不會成為項目中問題的根源,并保證在其開發過程中生成的任何代碼不會影響任何將其作為代碼庫的項目。
C 代碼特別容易受到移植問題的影響,特別是因為不能期望編譯器檢測到它們,因為代碼將符合語言規范(假設沒有使用語言擴展)。因此,開發人員必須使用靜態分析工具來確認移植將按計劃進行。靜態分析工具可以通過多種方式幫助解決此問題。
int 大小引起的可移植性問題
int 中的精度(位數)可能因系統而異。為了解決這個問題,通常定義一組 typedef 來將系統類型映射到應用程序類型。可以為 16 位架構定義以下示例:
typedef 無符號字符 U8;
typedef unsigned int U16;
typedef unsigned long U32;
如果將代碼移植到 32 位架構,則此示例可以更改為以下內容:
typedef 無符號字符 U8;
typedef 無符號短 U16;
typedef unsigned int U32;
然而,移植并不是那么簡單,因為 int 大小的變化會對代碼產生一些不太明顯的影響。例如,其結果取決于整數提升效果的任何表達式都可能表現出不同的行為。因此,只有在包含受影響類型的對象的所有表達式中的精度符合目的時,這種更改才合適。靜態分析可以用來驗證這個假設。
編譯器不會報告任何這些問題,因為代碼對于目標環境完全有效,即使它的行為可能不符合預期。
編譯器實現引起的可移植性問題
與編譯器相關的實現定義的、未指定的或未定義的行為的差異可能會導致移植時出現缺陷。
實現定義的行為是編譯器之間可能不同但由編譯器供應商記錄的行為。靜態分析工具可以檢測調用此類行為的代碼,以便將其消除以促進移植。
也可以檢測到未指定或未定義的行為;但是,它帶來的不僅僅是可移植性問題,因為這種行為可能會在同一編譯器的不同版本之間以未記錄的方式發生變化,甚至可能在同一編譯器內的各種用例之間發生變化。調用這種行為的代碼可以工作,但很可能會非常脆弱。值得注意的是,遷移到不同版本的編譯器可以被視為移植。
編譯器不需要檢測實現定義的、未指定的或未定義的行為的使用,因為代碼是完全有效的。
編碼標準
諸如 MISRA C:2004 (www.misra-c.com) 等公開可用的編碼標準,可以通過靜態分析工具嚴格執行,包括防御這些可移植性問題的規則。后面的例子使用了這個標準。
C 中的整數轉換
在 C 中對表達式求值期間,管理不同算術類型的隱式轉換方式和時間的規則很復雜。為確保移植代碼時結果符合預期,在考慮了所有此類隱式轉換后,表達式中的所有操作都應以相同的類型進行。
與整數提升相關的隱式轉換很容易導致代碼的執行方式與開發人員期望的方式大不相同。整數提升基本上要求將任何小于 int 的類型(例如 char、short)轉換為 int,然后再將其用作表達式中的操作數。許多嵌入式系統廣泛使用這些類型,因為它們通常允許更有效地使用內存資源,這可能會受到限制以節省成本、空間和功率。
整數提升是保值的(意味著保留大小和符號),但對象的符號可能會改變。此外,表達式將以比操作數類型更寬的類型進行計算。考慮以下示例:
U8 u8a = 200U;
U8 u8b = 100U;
U8 u8r = u8a + u8b;
在此示例中,u8a 和 u8b 在加法發生之前都被轉換為寬度至少為 16 位的有符號整數。然后將加法的結果隱式轉換回 8 位,然后再存儲到 u8r 中。在這種情況下,開發人員可能會期待結果 (44),因為可以合理地假設他們知道賦值時發生的模 2 運算。這意味著結果實際上與以 8 位精度進行操作時的結果相同(整數提升不影響結果)。
但是,當整數提升與隱式擴展轉換同時發生時,可能會造成混淆。考慮以下:
U16 u16a = 0xffffU;
U16 u16b = 0x0001U;
U32 u32r = u16a + u16b;
在 32 位架構上,u32r 的類型為 unsigned int,而 u16a 和 u16b 的類型為 unsigned short。整數提升將導致操作數在加法之前轉換為有符號整數,結果將在賦值時隱式轉換為無符號整數,最終值為 0x10000。開發人員可以(也許有理由)依靠發生的整數提升來確保加法不會像使用 16 位算術時那樣換行。
如果開發人員決定將代碼移植到 16 位架構,則 u32r 將具有 unsigned long 類型,而 u16a 和 u16b 將具有 unsigned int 類型。這一次,在加法發生之前(也在 unsigned int 中),不會對已經是 unsigned int 的操作數應用任何轉換,并且 0x0000 的結果將在賦值時隱式轉換為 unsigned long,最終值為0x0000。以更廣泛的類型執行添加的假設現在不再有效,并且存在發生意外回繞的風險。
這表明當代碼從一個平臺移植到另一個平臺時,它可以多么容易地表現出不同的行為。這里的真正問題與在分配結果時發生的隱式擴大轉換有關。這可以通過確保始終使用強制轉換以必要的精度評估表達式來消除,例如:
u32r = (u32) u16a + u16b;
( u32 ) 強制轉換確保表達式始終以具有適當精度的類型進行評估。在前面的示例中,這意味著表達式是以 unsigned long 而不是 unsigned int 計算的。如圖 1 所示,靜態分析可以很容易地檢測到隱式加寬。
圖 1: LDRA 工具套件等靜態分析工具可以突出顯示有效 C 代碼中的問題,這些問題在移植時可能導致功能錯誤。因為代碼是有效的 C,編譯器不會檢測到這些問題。
整數提升也會影響其他操作。考慮以下:
u16a = 0x1234U;
u16r = ~u16a 》》 8;
在 16 位架構上,這將導致 u16a 的位被反轉,頂部字節移入底部字節,將 0x00ED 分配給 u16r。
但是,在 32 位架構上,u16a 將在補碼發生之前轉換為帶符號的 int(32 位),從而將值 0xFFED 分配給 u16r。
再一次,使用強制轉換將確保行為符合預期:
u16r = ( U16 )~u16a 》》 8;
評估代碼適用性
靜態分析工具是代碼移植的寶貴幫助。如圖 2 所示,這些工具允許開發人員評估遺留代碼并確保以允許移植的方式開發新代碼。
圖 2:靜態分析工具報告,例如 LDRA 工具套件中的這個示例,可以有效評估代碼移植的適用性。
在項目生命周期中盡早采用靜態分析將確保盡早驗證遺留代碼,并確保任何新代碼從一開始就可移植。通過縮短開發時間和顯著降低殘留缺陷水平,開發人員可以快速收回使用此類工具所涉及的初始支出。
審核編輯:郭婷
-
C++
+關注
關注
22文章
2114瀏覽量
73785 -
編譯器
+關注
關注
1文章
1642瀏覽量
49231
發布評論請先 登錄
相關推薦
評論