捕捉 Lua 例外 |
|
Lua 版本: 5.0 (版本說明)
本文提供一組修正程式,用於簡化 C 和 C++ 中的 lua 例外處理。
在 Lua 腳本中產生的例外有兩種可能的結果
Lua 的 C API 提供函式 lua_pcall() 用於執行受保護呼叫 [1]。
當程式「引發」例外時,它會進行「非區域跳躍」。它會跳躍到可能不在同個區塊、同個函式、甚至同個模組的程式碼時間點。Lua 手冊將這些非區域跳躍的目的地稱為「復原點」。
由於 Lua 執行時期是用 C 編寫,因此它無法使用 C++ 例外,所以 Lua 會使用 C 函式 setjmp()
和 longjmp()
實作此行為 [2]。
注意:從 Lua 5.1 開始,當以 C++ 編譯時,Lua 會使用原生 C++ 例外,而不是 setjmp()
和 longjmp()
。
setjmp()/longjmp()
的問題setjmp()/longjmp()
慣用寫法非常有效率且易於使用。它用於許多 C 函式庫,以提供例外處理支援。但是,它並非沒有問題。
setjmp()
會將 CPU 執行環境的目前狀態(暫存器、堆疊指標等)儲存在一個緩衝區中。使用該緩衝區呼叫 longjmp()
,會復原保存的執行環境。基本上,longjmp()
會「時光倒流」回到 setjmp()
呼叫。程式會從那個時間點繼續執行,而它的執行環境看起來像沒有任何變動。
jmp_buf jb; char* mystr = "Original Value"; /* start */ int i_except = setjmp( jb ); if( 0 == i_except ) /* try */ { mystr = "New Value"; /* Throw an exception. Jumps back to "start", and setjmp() returns 33 */ longjmp( jb, 33 ); } else /* catch */ { /* Handle the exception */ } print( mystr );
許多編譯器會最佳化對區域變數的參照,方法是將它們的值快取到暫存器中。當程式碼執行順序執行時,這個方式很好。如果編譯器用盡暫存器,它可以將值儲存在記憶體中,然後稍後載入該值。但是,呼叫 longjmp()
會將 setjmp()
保存的暫存器狀態回復。如果編譯器已將變數的值快取在暫存器中,而它的值在 setjmp()
之後但在 longjmp()
之前變動,那麼在 longjmp()
復原其舊值時,新的值就會遺失。
在前述範例中,變數 mystr
容易發生這個問題。呼叫 print()
時,mayvar
可能包含「新值」,或可能包含「原始值」,這取決於編譯器如何最佳化程式碼。
-- 實際上,上述範例中 setjmp 呼叫的脈絡並不符合規定,而且產生的行為未定義。以下的範例在這方面是可以接受的。 -- Wim Couwenberg
這個問題可以使用謹慎的程式設計來減輕。解決方案有
setjmp()/longjmp()
之間變更區域變數。
setjmp()/longjmp()
區塊外部宣告的變數應該宣告為 volatile
。在上述範例中,將 mystr
宣告為 volatile
可以防止編譯器快取其值到暫存器
char* volatile mystr;
宣告一個 volatile int
volatile int myint;
請注意,函數參數必須包含在 volatile
變數中
int func( int* volatile pInt ) /* pointer declared volatile */ { jmp_buf jb; if( 0 == setjmp( jb ) ) { pInt++; longjmp( jb, 97 ); } else { } }