最近專案程式碼需要從mips平臺移植到x86平臺,這是公司產品第一次採用x86平臺。之前專案很緊,所以很多程式碼都沒有考慮移植性問題,因此移植的時候遇到了不少問題。前幾天才解決了位序(也叫位元序,與位元組序不同)問題,今天又遇到了一個比較隱蔽的C語言問題,在這裡記錄一下,告誡自己,也告誡各位同行,避免犯這樣的錯誤。至於位序問題,以後應該會再另寫一篇文章來說明。
原本在mips平臺上執行良好的程式碼,移植到了x86平臺,結果卻不對了,我們仔細分析了程式碼,沒發現什麼可疑的地方,而且我之前為了優化那段程式碼,單獨把那段程式碼抽出來測試過。我抽出來的程式碼在兩個平臺裡得出的都是一樣的結果。我對比了程式碼,實現的地方沒有任何改動,照理說不應該出現這種情況的。不過根據列印出來的值,我注意到了一種情況,在x86平臺裡的結果值只有16位,但在mips平臺裡的結果值有32位,並且低16位的值與x86平臺下的值一樣。最後,我檢視了宣告該函式的標頭檔案,才發現標頭檔案裡函式的宣告與C檔案裡的實現返回值不一致!
問題可以簡化成下面的程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
//crc.c //注意,此處沒有包含crc.h這個標頭檔案! unsigned int get_crc(void) { return 0x12345678; } //crc.h unsigned short get_crc(void); //main.c #include <stdio.h> #include "crc.h" int main(int argc, char *argv[]) { unsigned int crc = get_crc(); printf("crc:%x\n", crc); return 0; } |
編譯執行: gcc -Wall -o test main.c crc.c //好吧,-Wall也沒辦法報錯
在x86平臺下輸出:5678
我又分別在mips平臺和powerpc平臺下編譯執行了這段程式碼,同樣沒警告或者報錯。在mips平臺下輸出:12345678,在powerpc平臺下輸出:12345678
在簡化的程式碼裡,大家很容易就能看出是get_crc這個函式的宣告和定義(實現)不一致導致的問題,但在龐大的專案檔案裡,可能就沒那麼容易看出問題所在了。
我們在寫程式碼的時候,往往只注意函式的實現,對函式的宣告重視不足。在Linux平臺下,我們喜歡用cscope+ctags+vim來寫程式碼,修改或者瀏覽程式碼的時候也喜歡跳到函式定義處,變數宣告處,卻很少關注函式宣告,導致修改程式碼之後宣告和定義不一致的情形。
這並不只是程式新手才會出現的問題,工作幾年的程式設計師也可能會犯這樣的錯誤,出現問題的這段程式碼,就是出自一個已經工作了四年的同事之手。
也正是在這個時候,我才發現,我們之前的程式碼是有問題的,只是所謂的“得到了正確的結果”。
我起先認為對於這種情況,是個編譯器未定義形為,不同gcc版本對這種情況的處理可能不一樣,但我進行了一些測試,發現情況比我想象中的複雜。在powerpc平臺,gcc版本是3.3.x,mips平臺,gcc版本是4.3.x,在x86平臺,有兩個版本的編譯器,分別為4.1.x(centos),4,6.x(ubuntu)執行情況是mips平臺和powerpc平臺一樣,都是12345678,x86平臺下均為5678。mips和powerpc都是大端,x86是小端,至令我沒辦法判斷真正的問題在哪,是編譯器版本原因還是與大小端有那麼點關係。還望知道的朋友不吝賜教。
此外,我還測試了對於變數的情況,發現對於變數的處理,各個gcc版本不同平臺都是一致的,當然,由於大小端的關係,輸出結果會不同。大家有興趣可以試一下。
說了那麼多,只是想說明這個隱蔽的錯誤大家一不小心就很容易犯,而且後果也比較嚴重,得找到方法避免。解決辦法很簡單,那就是通過把函式宣告(原型)放在標頭檔案中,而函式定義則放在另一個包含了該標頭檔案的原始檔中。這樣編譯器就能發現不一致的情況從而報錯提醒我們。這個問題在《C專家程式設計》8.5節有論述。
我單獨提出來的程式碼之所以結果一致,是因為我把函式定義跟對該函式的引用都放一個檔案中了,沒有使用標頭檔案。
4月18日補充:由於之前mips平臺和x86平臺gcc版本不一樣,沒有可比性,今天到公司換了一樣的gcc版本進行測試,發現mips平臺下還是輸出12345678,這看來應該是編譯器後端的行為,有時間看下mips彙編確認一下吧。