這是4月去上課的時候作範例程式練習看到的case
有同學 (事) 照著教材給的範例程式碼照打之後運行
竟然發生Segmentation Fault (不正當存取記憶體)
簡述一下當時的觀察狀況
OS是Linux 64-bit
funcA 型態是 char* (int) → 一個回傳值為 char指標,參數為 int的函式
放在某個 include檔中
範例程式碼並沒有寫著要 include該檔案而直接使用
printf("%s\n", funcA(1));
編譯時無任何錯誤與警告 (註 1)
執行時發生 segmentation fault
但是 include之後就不會出錯
並且根據授課講師說法,以前並沒有出現此問題
那是什麼原因造成有無include而有如此大之差異?
關鍵在於這次上課使用的 OS是 64-bit
首先看著是 function的實作本身
一個實作完開放的函式模組,內部所回傳的值是一個 reference
在 64-bit系統上是一個 64-bit的數值
這是不會改變的
因此問題就在於外部怎麼對待該函式
C語言常識:「若未宣告function,參數、回傳值一切以 int作為預設型別」
(修正,僅有回傳值,見C - Type is a Big Problem之敘述)
以現代 C編譯器,int基本上被定義為 32-bit整數 (過去是 16-bit)
因此 funcA 之回傳值,原先應該是 64-bit 位址值
但因為隱含宣告為 int而造成回傳值實際上只有後 32-bit被取出並複製至 printf之參數列當中
即原先 0x########$$$$$$$$ → 0x00000000$$$$$$$$
接著 printf內部再將該數值再度作為 reference使用
並且很剛好也非常好 (註 2) 的是,該數值若作為記憶體位址解讀是一個違法記憶體位址
使得系統產生 segmentation fault終止程式
後來求證過去上課的系統使用的果然是 32-bit的 OS (註3)
至於筆者自身寫程式很乖
會隨時先注意該 include什麼檔案,所以沒出狀況
註 1
printf的型別是 int (const char*, ...)
即從第二個參數開始,是一個可變長度的參數列表
在 C語言當中,可變長度參數列表是不預先假設任何型
端看函式模組內部如何應用每個參數
因此即使型別不符合實際需要,編譯器仍舊不會出現任何訊息
但若是以 char指標儲存的話,就會出現警告訊息了
因此在此呼籲,任何一個警告都是不可忽視的
除非程式開發者心中只剩「型別只是輔助」的概念
註2
為啥說非常好,因為若是合法的記憶體位址
那絕對還是一個不正確位置的資料存取,但是系統卻不會出現任何錯誤
等到真的不小心爆發出來,即「怎麼死的都不曉得」
註3
32-bit系統中 reference數值與 int相同是 32-bit
因此不會出現任何問題,因此再次呼籲,寫 strongly-typed 語言,習慣要好一點
嗯...其實寫 loosely-typed語言習慣要更好 wwwwwwww
更新紀錄
2015.01.09,更正隱含宣告型別
留言列表