C 異常的幕後(1)

2021-09-11 16:35:50 字數 1053 閱讀 7434

每個人都知道良好的異常處理是困難的。在異常「生命期」的每個層面,出現這種情況的原因有許多:編寫異常安全的**是困難的,異常可能從不期望的位置丟擲(雙關語),理解設計不良的異常架構是複雜的,因為幕後發生了許多巫術,它是慢的;因為不正確地丟擲異常可能導致呼叫不可原諒的std::terminate,它是危險的。雖然每個曾經與「異常」程式鬥爭的人可能知道這,造成這種混亂的原因並不廣為人知。

我們要問自己的第乙個問題是,這一切是如何工作的。這是應該長系列的第一篇文章,在這個系列裡我將討論在c++裡,異常在幕後是如何實現的(實際上在x86平台上使用gcc編譯c++,不過這也可能適用於其他平台)。在這些文章中,將詳細解釋丟擲與捕捉異常的過程,但對那些沒有耐心的人這裡有小的乙個文章摘要:在gcc/x86中如何丟擲異常:

當我們編寫一條throw語句時,編譯器把它翻譯為對libstdc++的一對呼叫:分配異常,然後通過呼叫libstdc開始棧回滾過程。

對每條catch語句,編譯器將在這個方法主體後寫下一些特殊資訊,一張這個方法可以捕捉的異常表以及清理表(稍後再解釋清理表)。

隨著回滾器穿過棧,它將呼叫libstdc++提供的乙個特殊函式(稱為personality例程),這個函式檢查在棧裡的每個函式可以捕捉哪些異常。

如果沒有找到與這個異常相符的捕捉,呼叫std::terminate。

如果找到相符的捕捉,回滾器現在在棧頂開始啟動。

隨著回滾器第二次穿過棧,它將要求personality例程為這個方法執行清理。

這個personality例程將檢查當前方法上的清理表。如果有任何清理活動要執行,它將「跳轉」到當前棧幀並執行清理**。這將為在當前作用域裡分配的每個物件執行析構函式。

一旦回滾器到達可以處理這個異常的棧幀,它將跳轉到合適的catch語句裡。

在完成catch語句的執行時,將呼叫乙個清理函式來釋放該異常持有的記憶體。

這看起來已經相當複雜,而我們甚至還沒開始;對異常處理的所有複雜性而言,這是乙個短的、不準確的描述。

如果你實在好奇且希望開始閱讀有關異常處理的實現,那麼你可以從這裡開始,在後續幾篇文章裡我們將實現乙個完整的規範。我將嘗試使得這些文章更有指導性,更容易遵循,期望下次開始我們的abi時再見!

C 異常的幕後11 閱讀CFI表

nicolasbrailo 要從我們已經為我們的abi實現的personality函式裡正確處理異常,我們需要閱讀lsda 語言特定資料區 來了解哪個呼叫幀 即哪個函式 可以處理哪個異常,以及了解 可以找到著陸墊 catch塊 lsda是cfi格式的,我們將在本章裡學習如何讀它。讀cfi資料是相當直...

C 異常的幕後8 兩階段處理

nicolasbrailo 上一章以新增乙個 unwind 能夠呼叫的personality函式而結束。它沒做什麼,但它在那裡。我們已經實現的abi現在可以丟擲異常,捕捉也已經完成一半,但需要正確選擇catch塊 著陸墊 的personality函式目前有點傻。我們通過嘗試理解personality...

C 異常的幕後3 取悅鏈結器的ABI

在我們理解異常的路程上,我們發現重擔在libstdc 裡完成,如c abi說明的那樣。閱讀了一些鏈結器錯誤,我們上次推斷要處理異常我們需要c abi的輔助 我們建立了乙個丟擲異常的c 程式,把它與乙個c程式鏈結,發現編譯器有時把我們的throw指令翻譯為某些現在呼叫幾個libstdc 函式的物件來實...