這是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,更正隱含宣告型別

創作者介紹
創作者 wylokgo101 的頭像
wylokgo101

豆棚瓜架雨如絲 - WYLOKGO101

wylokgo101 發表在 痞客邦 留言(0) 人氣()