最短的崩潰程式(C語言版)

老碼農發表於2013-06-02

想寫個崩潰的C語言小程式,看起來是個奇怪的主意,不過在我曾經教過的一門實驗課上,這是作業之一!實際上,這是一件非常有教學意義的事情。

通常學生們要麼嘗試反向引用一個非法地址,要麼就是除0.除0會引發SIGFPE訊號(浮點異常)。這裡有一個小例子程式,使用除零方法來使之崩潰:

我們也可以刪掉return關鍵字,但是當我這麼做的時候gcc不會為這些語句生成可執行程式碼,即便優化選項被disable掉了。我們還可以通過把上面的語句改成賦值語句,使上面的程式碼改變一些特徵:

注意我宣告瞭一個沒有型別的i。這樣的程式碼在C89標準裡是有效的,因為所有的宣告都有隱形的預設型別int。在C99和其他一些C標準裡這是一個錯誤。假定我們寫的是C89程式碼,那麼我們甚至可以使用隱形int來宣告main函式:

那是相當短的程式碼了 — 如果我們不把用於縮排的空格計算進來,只有16個字元。然而,我們還可以做得更好!

當C程式在編譯的時候,編譯器會產生一個或更多物件檔案,檔案裡有對於用到的庫和全程物件(函式和變數)的符號索引。然後這些物件檔案會被進行連結,這時符號索引被地址所代替,就產生了一個可執行檔案。

編譯器在一個物件檔案裡提供了一個呼叫main函式的入口點。呼叫main函式意味著我們試圖執行在儲存在main函式連結的位置所對應地址裡的指令。

有趣的是,連結器對於不同物件的型別是沒有概念的,它只知道它們的地址。所以,如果我們用一個常規的全程變數替換main函式,編譯器會高興地build物件檔案,因為它不關心物件main的型別是什麼;連結器也會高興地連結它,因為它只關心main函式對應的地址。

所以,考慮這個C程式:

這個程式會編譯成一個可執行檔案,它會試圖呼叫地址0,而0並不是我們能夠訪問的地址,這樣我們會得到SIGSEGV訊號(分段錯誤)。

更正:我前面關於這個程式崩潰的原因分析是錯的。這個程式會試圖按函式方式去執行main,而這樣不會奏效,因為編譯器把它放到了不可執行的資料段。所以變數main初始化為什麼值都無所謂了。(感謝Zack的糾正)

現在我們已經非常接近最小的崩潰的C程式了。我們可以利用這個技巧,配合隱形int型別,來把它進一步縮短。

還有,C裡的全域性變數都會隱形地初始化為0,所以上面的程式碼就等同於:

好了,現在我們得到了最短的崩潰的C程式!

補充:

Hacker News使用者femto指出,編譯和連結一個空檔案也是可能的。我沒有釋出這個是因為gcc會拒絕編譯和連結這樣的程式,它會要求分開編譯和連結的過程。

另外,要是我們再學究一點,我應該指出我這裡的“全域性”變數意思是說“靜態”變數。

打賞支援我翻譯更多好文章,謝謝!

打賞譯者

打賞支援我翻譯更多好文章,謝謝!

任選一種支付方式

最短的崩潰程式(C語言版) 最短的崩潰程式(C語言版)

相關文章