跳轉到內容

使用 C 和 C++ 的程式語言概念/異常處理

來自華夏公益教科書

C 中的異常和錯誤處理,如果有的話,是透過特殊返回值來完成的,這些返回值不能由函式呼叫的正常完成產生。例如,考慮 printf 或它的其中一個朋友。如果一切正常,對這些函式的呼叫通常會返回傳送到輸出流的字元數。如果出現錯誤,EOF[1] 會被返回 - 這是一個在一切正常的情況下永遠不會被返回的值。

或者,假設你實現了一個函式,該函式查詢容器中特定專案的出現次數。這通常意味著返回零或一個正整數。如果容器不存在,並且你想將其作為反饋提供,該怎麼辦?返回零沒有幫助;這意味著容器中沒有專案。一個正值也無濟於事。返回一個負值,比如 -1 怎麼樣?

這回答了我們對處理異常情況的擔憂。但是,根據規範編寫程式碼不會很有趣。考慮以下程式碼片段,其中程式設計師盡其所能為每個函式呼叫提供控制。

... ret = f(...); if (ret < 0) { if (ret == RETF_ABNORMAL_1) do-something-1; else if (ret == RETF_ABNORMAL_2) do-something-2; ... else do-something-n; } /* end of if (ret < 0)*/ ... ret = g(...); if (ret != NO_ERROR) { ... ... } /* end of if (ret != NO_ERROR) */ ...

看起來很亂,對吧?弄清楚控制流是一項難以忍受的任務,需要遍歷無數的 ifelse-if。程式碼充斥著錯誤處理程式碼,這使得維護人員難以維護。[2] 它幾乎讓你認為情況不會更糟。

如果我們能夠保證程式無故障,上面的程式碼將縮減為兩行,並且更容易閱讀和理解。或者,如果我們能夠將錯誤處理程式碼與其他程式碼隔離,那將非常棒。這正是許多語言中找到的異常處理機制所做的:隔離處理意外條件的程式碼部分,以便更輕鬆地維護程式。

然而,在 C 中這是不可能的。在 C 中,你必須要麼走眾所周知的路徑[並用無數個 ifelse-if 填充你的程式碼],要麼使用 setjmp-longjmp 對。在本講義中,我們將看看後者,並介紹兩個互補的概念,斷言訊號

但是,在我們繼續之前 - 為了給異常處理機制的內部工作提供一些啟發 - 我們將簡單介紹一下 Win32 作業系統中是如何處理異常的。[3]

Win32 中的異常處理:結構化異常處理 (SEH)

[編輯 | 編輯原始碼]

Win32 中的異常處理的基礎是異常的註冊。這是透過將異常記錄插入到一個連結結構中來完成的,該結構的第一個節點由 FS:[0] 指向;當 try-catch 塊進入和退出時,分別插入和刪除新記錄。

為了簡單起見,編譯(並連結)以下 C 程式並執行可執行檔案。[4]

Exc_Test.c
#include <windows.h>
#include <stdio.h>

DWORD scratch = 2;

下一個函式包含對 EXCEPTION_ACCESS_VIOLATION 的處理程式碼,這意味著執行緒嘗試訪問一些無法訪問的記憶體。這可能是由於嘗試寫入記憶體的只讀部分或從沒有相應許可權的區域讀取造成的。由於此異常是透過將一些有效地址值移入 EAX 來處理的,因此我們使用 ExceptionContinueExecution 返回,這意味著將再次嘗試生成異常的指令。

如果丟擲的異常不是 EXCEPTION_ACCESS_VIOLATION,則透過返回 ExceptionContinueSearch 將控制權轉移到列表中的下一個處理程式。

struct _CONTEXT,不出所料,是一個依賴於 CPU 的結構,它由包含機器狀態的欄位組成,而 ContextRecord 用於儲存異常發生時所做的快照。與其相輔相成的是一個獨立於 CPU 的結構,其中包含有關最近發生的異常的資訊,例如它的型別、發生異常的指令地址等等。當發生異常時,作業系統將這些結構以及一個包含指向它們的每個結構的指標的第三個結構推送到引發異常的執行緒的堆疊上。

EXCEPTION_DISPOSITION __cdecl av_handler(
  struct _EXCEPTION_RECORD *ExcRec,
  void* EstablisherFrame,
  struct _CONTEXT *ContextRecord,
  void* DispatcherContext) {
  printf("In the handler for ACCESS_VIOLATION\n");
  if (ExcRec->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
    ContextRecord->EAX = (DWORD) &scratch;
    printf("Handled access violation exception...\n");
  } else {
      printf("Cannot handle exceptions other than acc. violation!\n");
      printf("Moving on to the next handler in the list\n");
      return ExceptionContinueSearch;
    }

  return ExceptionContinueExecution;
} /* end of EXCEPTION_DISPOSITION av_handler(.....) */

這是我們的第二個處理程式函式,它處理[整數]除以零異常。程式碼沒有新東西!

不過,值得一提的是:處理程式函式可以合併成一個。沒有規定說每個異常都必須有自己的處理程式函式。在我們的例子中,以下函式也能達到目的。

EXCEPTION_DISPOSITION __cdecl common_handler( struct _EXCEPTION_RECORD *ExcRec, void* EstablisherFrame, struct _CONTEXT *ContextRecord, void* DispatcherContext) { printf("In the shared handler\n"); if (ExcRec->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO) { printf("Handled divide by zero exception...\n"); ContextRecord->EBX = (DWORD) scratch; } else if (ExcRec->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) { ContextRecord->EAX = (DWORD) &scratch; printf("Handled access violation exception...\n"); } else { printf("Cannot handle exceptions other than div. by zero and acc. violation\n"); printf("Moving on to the next handler in the list\n"); return ExceptionContinueSearch; } return ExceptionContinueExecution; } /* end of EXCEPTION_DISPOSITION common_handler(.....) */ ... DWORD sole_handler = (DWORD) &common_handler; ...

EXCEPTION_DISPOSITION __cdecl dbz_handler(
  struct _EXCEPTION_RECORD *ExcRec,
  void* EstablisherFrame,
  struct _CONTEXT *ContextRecord,
  void* DispatcherContext) {
    printf("In the handler for INT_DIVIDE_BY_ZERO\n");
    if (ExcRec->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO) {
      printf("Handled divide by zero exception...\n");
      ContextRecord->EBX = (DWORD) scratch;
    } else {
        printf("Cannot handle exceptions other than div. by zero\n");
        printf("Moving on to the next handler in the list\n");
        return ExceptionContinueSearch;
      }

  return ExceptionContinueExecution;
} /* end of EXCEPTION_DISPOSITION dbz_handler(.....) */

int main(void) {
  DWORD handler1 = (DWORD) &av_handler;
  DWORD handler2 = (DWORD) &dbz_handler;

以下彙編程式碼塊為可能的異常註冊了處理程式。它透過將 _EXCEPTION_REGISTRATION_RECORD(一個用於訪問衝突,另一個用於除零錯誤)插入到用於儲存有關異常處理程式資訊的連結列表的頭部來實現這一點。這個 [異常註冊] 結構有兩個欄位:一個指向先前結構的指標,即列表中的下一個節點,以及一個指向回撥函式的指標,該函式在異常發生時被呼叫。

這幾乎是 Win32 編譯器在進入 `try` - `block` 時生成的程式碼片段:它將相關 `catch` 塊的程式碼註冊為 `handlers2`,方法是將它們新增到一個列表的頭部,該列表的第一個專案由 `FS:[0]` 中的值指向。在完成 `try` 塊後,在入口處註冊的任何處理程式都將從列表中刪除。

  __asm {
    PUSH handler1      ; push address of av_handler
    PUSH FS:[0]
    MOV FS:[0], ESP

    PUSH handler2      ; push address of dbz_handler
    PUSH FS:[0]
    MOV FS:[0], ESP
  }

當控制到達這裡時,與異常處理相關的記憶體部分影像將如下所示。[5] 從 `av_handler` 記錄中發出的指標指向預設處理程式,該處理程式將被呼叫來處理任何未處理的異常。

Exception handler list

接下來,我們嘗試將 1 儲存到一個 4 位元組的記憶體區域中,該區域的起始地址包含在 `EAX` 中。但是,我們的嘗試將導致一個異常,因為 `EAX` 包含 0,並且地址 0 對 Win32 系統中的程序來說是禁區。[6]

  __asm {
    MOV EAX, 0
    MOV [EAX], 1
  }

  __asm {
    MOV EAX, 6
    MOV EDX, 0
    MOV EBX, 0
    DIV EBX
  }

下一個彙編程式碼塊刪除了在進入 `main` 時插入的異常記錄。[7] 這與 Win32 編譯器在退出 `try` - `catch` 塊時生成的程式碼大致相同。

  __asm {
    MOV EAX, [ESP]
    MOV FS:[0], EAX
    ADD ESP, 8

    MOV EAX, [ESP]
    MOV FS:[0], EAX
    ADD ESP, 8
  }

  printf("Will intentionally try dividing by zero once more!\n");

現在 `dbz_handler` 已從列表中刪除,在以下程式碼塊中丟擲的除零錯誤異常將由預設處理程式(即 `UnhandledExceptionFilter` 系統函式)處理,該函式只是顯示一個眾所周知的、最煩人的訊息框。</syntaxhighlight>

  __asm {
    MOV EAX, 6
    MOV EDX, 0
    MOV EBX, 0
    DIV EBX
  }

  return 0;
} /* end of int main(void) */
cl /w /FeExcTest.exe Exc_Test.c /link /SAFESEH:NO↵
ExcTest↵
In the handler for INT_DIVIDE_BY_ZERO
Cannot handle exceptions other than div. by zero
Moving on to the next handler in the list
In the handler for ACCESS_VIOLATION
Handled access violation exception...
Handled divide by zero exception...
Will intentionally try dividing by zero once more!
插入圖片

使用 Microsoft 擴充套件在 C 中進行異常處理

[edit | edit source]

使用 Microsoft 擴充套件,您可以編寫具有異常處理功能的 [不可移植的] C 程式,儘管方式略有不同。以下是一個簡單的例子。

Exc_SEH.c
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

int *iptr = 0;
int zero = 0;
int res;

以下是一個異常過濾器函式的例子。 _EXCEPTION_FILTER_ 決定在發生異常時要採取什麼行動。在這樣做時,它可以根據異常的型別做出不同的反應。例如,我們的過濾器函式在發生訪問衝突異常時提供了一些補救措施,而對於其他型別的異常,它將決策推遲到列表中的下一個處理程式。

LONG av_filter(DWORD exc_code) {
  if (exc_code == EXCEPTION_ACCESS_VIOLATION) {
    printf(In the handler for ACCESS_VIOLATION\n);
    iptr = (int *) malloc(sizeof(int));
    *iptr = 0;
    printf(Handled access violation exception...);
    return EXCEPTION_EXECUTE_HANDLER;
    } else return EXCEPTION_CONTINUE_SEARCH;
} /* end of LONG av_filter(DWORD) */

int main(void) {

類似於程式語言級別提供的結構,Microsoft SEH 有 _GUARDED REGION_(`__try`)的概念,它後面是 _HANDLER CODE_(`__except` 或 `__finally`)。一個主要區別是處理程式的數量:在 SEH 中,可以在一個特定的 `__try` 後面使用 `__except` 或 `__finally` 中的任意一個,並且只能使用一次。

在執行受保護區域之前,相關的處理程式行(18 到 22 行)透過編譯器合成的程式碼向作業系統註冊,這與上一節的 39 到 46 行大致相同。在此之後,受保護區域被執行並導致異常,該異常透過傳遞給 `__except` 的表示式進行過濾。在我們的例子中,過濾器決定如果這是一個除零異常,則繼續執行當前處理程式;否則,它將決策推遲到列表中的下一個處理程式。

當控制到達第 23 行時,由於編譯器合成的程式碼,同一個處理程式將被取消註冊,這與上一節的 57 到 64 行大致相同。

  __try { res = 2 / zero; } 
  __except(GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
    printf(Knew this would happen...);
    res = 3;
  }
  printf( res: %d\n, res);

下一個 `__except` 不是一個簡單的過濾器,而是一個過濾器函式,它具有完全相同的功能。

  __try { *iptr = 0; }
  __except(av_filter(GetExceptionCode())) { }
  printf( *iptr: %d\n, *iptr);
  free(iptr);

由於所有使用者編寫的處理程式都被拉了下來,因此執行第 29 行導致的異常將由作業系統提供的 _預設處理程式_ 處理,該處理程式負責處理所有未處理的異常。

  printf(Will intentionally try dividing by zero once more!\n);
  res = 2 / zero;

	return 0;
} /* end of int main(void) */
cl /FeExcSEH.exe Exc_SEH.c↵
ExcSEH↵
...
...
...

模組

[edit | edit source]

介面

[edit | edit source]
List.h
#ifndef LIST_H
#define LIST_H

以下指令是必需的,它引入型別定義和函式原型,以便進行跨函式邊界跳轉。除了 `setjmp` 和 `longjmp` 的原型之外,該標頭檔案還包含用於緩衝區型別的定義,該型別例項用於在上述函式之間進行通訊。

#include <setjmp.h>

#include "General.h"
#include "ds/ListIterator.h"

struct LIST;
typedef struct LIST* List;

typedef struct _Object_funcs {
  COMPARISON_FUNC compare_component;
  COPY_FUNC copy_component;
  DESTRUCTION_FUNC destroy_component;
} List_Component_Funcs;

typedef enum {ITERATOR_BACKWARD, ITERATOR_FORWARD} Iterator_type;

#define EXC_LIST_EMPTY -21

#define FROM_ARRAY 2

extern List List_Create(int constructor_type, ...);
extern void List_Destroy(List*);

`jmp_buf` 是一個機器相關的[8] 緩衝區,用於儲存程式狀態資訊。我們所做的基本上是在執行可能產生異常的程式碼塊之前填充該緩衝區,並且如果丟擲異常,則使用其中找到的值來產生執行程式碼塊之前存在的環境。

extern Object List_GetFirst(const List this, jmp_buf get_first_caller)
  /* throws ListEmpty */ ;
extern Object List_GetLast(const List this, jmp_buf get_last_caller) 
  /* throws ListEmpty */ ;

extern void List_InsertAtEnd(const List, Object new_item);
extern void List_InsertInFront(const List, Object new_item);

extern Object List_RemoveFromEnd(const List this, jmp_buf rmv_from_end_caller)
  /* throws ListEmpty */ ;
extern Object List_RemoveFromFront(const List this, jmp_buf rmv_from_front_caller)
  /* throws ListEmpty */ ;

extern BOOL List_IsEmpty(const List);
extern int List_Size(const List);

extern List List_Merge(const List, const List otherList);
extern Object* List_ToArray(const List this, jmp_buf to_array_caller)
  /* throws ListEmpty */ ; 

extern ListIterator List_ListIterator(const List, Iterator_type it_type);

#endif


ListExtended.h
#ifndef LISTEXTENDED_H
#define LISTEXTENDED_H

#include <setjmp.h>

#include "General.h"
#include "ds/List.h"

#define EXC_LIST_NOSUCHITEM -26

extern void List_InsertAfterFirst(const List, Object, Object, jmp_buf ins_after_first_caller)
  /* throws NoSuchItem */ ;
extern void List_InsertAfterLast(const List, Object, Object, jmp_buf ins_after_last_caller)
  /* throws NoSuchItem */ ;
extern void List_InsertAfterNth(const List, Object, Object, int, jmp_buf ins_after_Nth_caller)
  /* throws NoSuchItem */ ;

extern void List_InsertBeforeFirst(const List, Object, Object, jmp_buf ins_before_first_caller)
  /* throws NoSuchItem */ ;
extern void List_InsertBeforeLast(const List, Object, Object, jmp_buf ins_before_last_caller)
  /* throws NoSuchItem */ ;
extern void List_InsertBeforeNth(const List, Object, Object, int, jmp_buf ins_before_Nth_caller)
  /* throws NoSuchItem */ ;

extern void List_InsertInAscendingOrder(const List, Object);
extern void List_InsertInAscendingOrderEx(const List, Object, COMPARISON_FUNC);
extern void List_InsertInDescendingOrder(const List, Object);
extern void List_InsertInDescendingOrderEx(const List, Object, COMPARISON_FUNC);

extern int List_RemoveAll(const List, Object, jmp_buf rmv_all_caller)
  /* throws ListEmpty */ ;
extern void List_RemoveFirst(const List, Object, jmp_buf rmv_first_caller)
  /* throws ListEmpty, NoSuchItem */ ;
extern void List_RemoveLast(const List, Object, jmp_buf rmv_last_caller)
  /* throws ListEmpty, NoSuchItem */ ;
extern void List_RemoveNthFromEnd(const List, Object, int, jmp_buf rmv_Nth_from_end_caller)
  /* throws ListEmpty, NoSuchItem */ ;
extern void List_RemoveNthFromFront(const List, Object, int, jmp_buf rmv_Nth_from_front_caller)
  /* throws ListEmpty, NoSuchItem */ ;

#endif


ListIterator.h
#ifndef LISTITERATOR_H
#define LISTITERATOR_H

#include <setjmp.h>

#include "General.h"

#define EXC_ITERATOR_ILLEGALSTATE -31
#define EXC_ITERATOR_ILLEGALARGUMENT -32
#define EXC_ITERATOR_NOSUCHELEMENT -33
#define EXC_ITERATOR_UNSUPPORTEDOPERATION -34

struct LISTITERATOR;
typedef struct LISTITERATOR* ListIterator;

extern void ListIterator_Destroy(ListIterator*);

extern BOOL ListIterator_HasNext(const ListIterator);
extern Object ListIterator_Next(const ListIterator, jmp_buf next_caller)
  /* throws NoSuchElement */ ;
extern int ListIterator_NextIndex(const ListIterator);

extern BOOL ListIterator_HasPrevious(const ListIterator);
extern Object ListIterator_Previous(const ListIterator, jmp_buf prev_caller)
  /* throws NoSuchElement */ ;
extern int ListIterator_PreviousIndex(const ListIterator);

extern void ListIterator_Remove(const ListIterator, jmp_buf  rmv_caller)
  /* throws IllegalState, UnsupportedOperation */ ;
extern void ListIterator_Add(const ListIterator, Object, jmp_buf add_caller)
  /* throws IllegalState, UnsupportedOperation */ ;
extern void ListIterator_Set(const ListIterator, Object, jmp_buf set_caller)
  /* throws IllegalArgument, IllegalState, UnsupportedOperation */ ;

#endif

實現

[edit | edit source]
List.c
#include <setjmp.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

#include "General.h"
#include "ds/ListExtended.h"
#include "ds/ListIterator.h"

#define IT_OPERATION_NOOP 0
#define IT_OPERATION_ADD 1
#define IT_OPERATION_NEXT 2
#define IT_OPERATION_PREVIOUS 3
#define IT_OPERATION_REMOVE 4
#define IT_OPERATION_SET 5

似曾相識:一個指向某些元資料的控制代碼,該元資料又包含一個指向容器的指標,該容器儲存集合的元件。

Underlying structure of a list

當您設計集合時,這種重複模式是一個很好的起點。控制代碼的存在是為了防止使用者直接操作資料結構,並且對於維護也很有用,因為它佔用恆定的記憶體量;元資料用於儲存有關結構的或/和結構的屬性的狀態資訊;容器用於物理地儲存資料。

觀察到我們使用了尚未定義的型別(struct _LIST)。編譯器對此沒有任何抱怨,因為我們沒有使用型別本身,而是使用了指向該型別的指標,而該指標的大小是已知的。

struct LIST {
  COMPARISON_FUNC compare_component;
  COPY_FUNC copy_component;
  DESTRUCTION_FUNC destroy_component;
  unsigned int size;
  struct _LIST* head;
};

以下是單個列表節點的定義。這個定義與列表緊密耦合,可以像下面這樣移動到列表型別定義中。

struct LIST { COMPARISON_FUNC compare_component; COPY_FUNC copy_component; DESTRUCTION_FUNC destroy_component; unsigned int size; struct _LIST { Object info; struct _LIST* next; struct _LIST* prev; }* head; };

以下定義的 Java 版本涉及使用正在定義的型別名稱,而在 C 中這是不可能的。在 C 中,型別名稱不能在完成定義之前使用。然而,對於指標來說,這不是問題。無論它們操作的物件的大小如何,它們的大小都不會改變。因此,每當我們在資料型別的描述中使用遞迴時,它很可能用指標來表示。[9]

struct _LIST {
  Object info;
  struct _LIST* next;
  struct _LIST* prev;
};
typedef struct _LIST* _List;

ListIterator 物件執行的所有操作實際上都會作用於底層的 List 物件。現在,我們可以在一個應用程式中擁有多個 List 物件,並且特定的迭代器可以用來遍歷其中一個,除了迭代器特定的欄位外,我們的結構還包含對封閉的 List 結構的引用。

Structure of the iterator

這類似於 Java 中內部類的實現方式:當轉換為 Java 1.0 以便生成 Java 虛擬機器位元組碼時,每個非靜態內部類建構函式的簽名都會被修改,以便它將封閉例項作為第一個引數接收。一旦建構函式開始控制,此值就會儲存在 private 欄位中。例如,

List.java

public class List implements IListExtended { public List() { ... } ... ... private class Iterator implements ListIterator { private Iterator(int type) { ... } ... } // end of inner class Iterator ... ... } // end of class List

將被轉換為

List.java

public class List implements IListExtended { public List() { ... } ... ... } // end of class List class List$Iterator implements ListIterator { // This corresponds to the field we named underlying_list!!! private List this$0; List$Iterator(List this$0, int type) { this.this$0 = this$0; ... } // end of constructor(int) ... ... } // end of inner class Iterator

請注意 List$Iterator 類的訪問說明符:包級可見,而不是 private。這是因為頂層類不能是 privateprotected。但是,為什麼不使用 public 呢?答案是因為在一個檔案中只能有一個 public 類,而恰好是 List 類。

但是,將 Iteratorprivate 提升到包級可見意味著它可以被原本不打算使用的類使用。答案在於合成類名的使用:這樣的名稱不能在原始碼中使用,並且這是由編譯器強制執行的。

struct LISTITERATOR {
  List underlying_list;
  _List ptr;
  int index;
  int last_op;
};

static _List create_sublist(Object info);

List List_Create(int type, ...) {
  int i, array_len;
  jmp_buf li_n;
  va_list ap;
  List_Component_Funcs funcs;
  ListIterator itr;
  List existing_list;
  Object* from_array;

  List ret_list = (List) malloc(sizeof(struct LIST));	
  if (!ret_list) {
    fprintf(stderr, "Out of memory...\n");
    return(NULL);
  } /* end of if(!ret_list) */

  ret_list->size = 0;
  ret_list->head = NULL;
  va_start(ap, type);
  switch(type) {
    case COPY:
      existing_list = va_arg(ap, List);
      ret_list->compare_component = existing_list->compare_component;
      ret_list->copy_component = existing_list->copy_component;
      ret_list->destroy_component = existing_list->destroy_component;
      itr = List_ListIterator(existing_list, ITERATOR_FORWARD);
      for(; ListIterator_HasNext(itr); )
        List_InsertAtEnd(ret_list, existing_list->copy_component(ListIterator_Next(itr, li_n)));
      free(itr);
      break;
    case DEFAULT:
      funcs = va_arg(ap, List_Component_Funcs);
      ret_list->compare_component = funcs.compare_component;
      ret_list->copy_component = funcs.copy_component;
      ret_list->destroy_component = funcs.destroy_component;
      break;
    case FROM_ARRAY:
      funcs = va_arg(ap, List_Component_Funcs);
      from_array = va_arg(ap, Object*);
      array_len = va_arg(ap, int);
      ret_list->compare_component = funcs.compare_component;
      ret_list->copy_component = funcs.copy_component;
      ret_list->destroy_component = funcs.destroy_component;
      for (i = 0; i < array_len; i++)
        List_InsertAtEnd(ret_list, ret_list->copy_component(from_array[i]));
      break;
  } /* end of switch(type) */
  va_end(ap);

  return ret_list;
} /* end of List List_Create(List_Constructor_type, ...) */

void List_Destroy(List* this) {
  int size, i;

  if (*this == NULL) return;

  size = (*this)->size; i = 1;
  for (; i <= size; i++) 
    (*this)->destroy_component(List_RemoveFromFront(*this));
  free(*this);
  *this = NULL;
} /* end of void List_Destroy(List*) */

Object List_GetFirst(const List this, jmp_buf gfc) /* throws List_Empty */ {

longjmp 是一個函式,它提供跨函式邊界的跳轉。這樣做時,它會利用以前由對 setjmp 函式的呼叫填充的 jmp_buf 結構。

在某種程度上,longjmp 可以被看作是“激素版的跳轉”。除了跳轉到其他指令(可能在不同的函式中)之外,它還透過使用 jmp_buf 結構中找到的值來恢復機器狀態,以確保程式處於有效狀態。因此,以下 longjmp 將跳轉到發出相應 setjmp 的位置,並在同時展開執行時堆疊。這樣做時,它會在傳遞給它的第二個引數中返回有關異常情況性質的資訊,在本例中,它應該反映出我們想要窺視的列表是一個空列表的事實。

請注意,此展開過程不會撤消對記憶體或檔案系統進行的任何修改;它只是重新建立機器狀態,就像之前一樣,對主記憶體和輔助記憶體內容所做的任何更改都將保留。如果您可能需要更完整的展開,您必須自行負責。

這種行為類似於異常:當丟擲的異常沒有在當前子程式中處理時,執行時堆疊會被展開,並且控制權將被轉移回呼叫方——這實際上是跨越當前函式邊界的跳轉——希望它能處理該異常。

  if (List_IsEmpty(this)) longjmp(gfc, EXC_LIST_EMPTY);

  return this->head->info;
} /* end of Object List_GetFirst(const List) */

Object List_GetLast(const List this, jmp_buf glc) /* throws List_Empty */ {
  if (List_IsEmpty(this)) longjmp(glc, EXC_LIST_EMPTY);

  return this->head->prev->info;
} /* end of Object List_GetLast(const List, jmp_buf) */

static _List create_sublist(Object info) {
  _List this = (_List) malloc(sizeof(struct _LIST));
  if (!this) return NULL;

  this->info = info;
  this->prev = this->next = this;

  return this;
} /* end of _List create_sublist(Object) */

void List_InsertAtEnd(const List this, Object new_item) {
  _List new_sublist = create_sublist(new_item);

  this->size++;
  if (List_IsEmpty(this)) this->head = new_sublist;
    else {
      _List tail = this->head->prev;      
      new_sublist->prev = tail;
      tail->next = new_sublist;
      new_sublist->next = this->head;
      this->head->prev = new_sublist;
    } /* end of else */
} /* end of void List_InsertAtEnd(const List, Object) */

void List_InsertInFront(const List this, Object new_item) {
  _List new_sublist = create_sublist(new_item);

  this->size++; 
  if (List_IsEmpty(this)) this->head = new_sublist;
    else {
      _List head = this->head;
      _List tail = this->head->prev;
      this->head = new_sublist;
      new_sublist->next = head;
      new_sublist->prev = tail;
      tail->next = new_sublist;
      head->prev = new_sublist;
    } /* end of else */
} /* end of void List_InsertInFront(const List, Object) */

Object List_RemoveFromEnd(const List this, jmp_buf rec) /* throws ListEmpty */ {
  _List tail;
  Object retVal;

  if (List_IsEmpty(this)) longjmp(rec, EXC_LIST_EMPTY);

  this->size--;
  tail = this->head->prev;
  retVal = tail->info;
  if (this->size != 0) {
    _List new_tail = tail->prev;
    new_tail->next = this->head;
    this->head->prev = new_tail;
  } else this->head = NULL;

  tail->next = tail->prev = NULL;
  tail->info = NULL;
  free(tail); 

  return retVal;
} /* end of Object List_RemoveFromEnd(const List, jmp_buf) */

Object List_RemoveFromFront(const List this, jmp_buf rfc) /* throws ListEmpty */ {
  _List head;
  Object retVal;

  if (List_IsEmpty(this)) longjmp(rfc, EXC_LIST_EMPTY);

  this->size--;
  head = this->head;
  retVal = head->info;
  if (this->size != 0) {
    head->prev->next = head->next;
    head->next->prev = head->prev;
    this->head = head->next;
  } else this->head = NULL;

  head->next = head->prev = NULL;
  head->info = NULL;
  free(head); 

  return retVal;
} /* end of Object List_RemoveFromFront(const List, jmp_buf) */

BOOL List_IsEmpty(const List this) {
  if (this->head == NULL) return TRUE;
    else return FALSE;
} /* end of BOOL List_IsEmpty(const List) */

int List_Size(const List this) { return this->size; }

Object* List_ToArray(const List this, jmp_buf tac) /* throws ListEmpty */ {
  int size = this->size, i;
  jmp_buf li_n;
  ListIterator itr;
  Object* ret_array;

  if (size == 0) longjmp(tac, EXC_LIST_EMPTY);

  ret_array = malloc(sizeof(Object) * size);
  if (!ret_array) {
    fprintf(stderr, Out of memory...\n);
    return(NULL);
  } /* end of if(!ret_array) */

  i = 0; itr = List_ListIterator(this, ITERATOR_FORWARD);
  for (; ListIterator_HasNext(itr); i++)
    ret_array[i] = ListIterator_Next(itr, li_n);
  free(itr);

  return ret_array;
} /* end of Object[] List_ToArray(const List, jmp_buf) */

List List_Merge(const List this, const List second_list) {
  jmp_buf li_n;
  List_Component_Funcs funcs = { this->compare_component, this->copy_component, this->destroy_component };
  List ret_list = List_Create(DEFAULT, funcs);
  List l1 = this, l2 = second_list;
  ListIterator itr;

  itr = List_ListIterator(l1, ITERATOR_FORWARD);
  for(; ListIterator_HasNext(itr);)
    List_InsertAtEnd(ret_list, 
  ret_list->copy_component(ListIterator_Next(itr, li_n)));
  free(itr);

  itr = List_ListIterator(l2, ITERATOR_FORWARD);
  for(; ListIterator_HasNext(itr);)
    List_InsertAtEnd(ret_list,
  ret_list->copy_component(ListIterator_Next(itr, li_n)));
  free(itr);

  return ret_list;
} /* end of List List_Merge(const List, const List) */

ListIterator List_ListIterator(const List this, Iterator_type it_type) {
  ListIterator ret_iterator = (ListIterator) malloc(sizeof(struct LISTITERATOR));
  if (!ret_iterator) {
    fprintf(stderr, Out of memory...\n);
    return(NULL);
  } /* end of if(!ret_iterator) */

  ret_iterator->underlying_list = this;
  ret_iterator->ptr = this->head; 
  ret_iterator->last_op = IT_OPERATION_NOOP;
  if (it_type == ITERATOR_FORWARD) ret_iterator->index = 0;
    else ret_iterator->index = this->size;

  return ret_iterator;
} /* end of ListIterator List_ListIterator(const List, Iterator_type) */

void List_InsertAfterFirst(const List this, Object new_item, Object after, jmp_buf iafc)
  /* throws NoSuchItem */ {
  jmp_buf ins_a_Nth;

執行 setjmp 命令將使用反映其呼叫時機器狀態的值填充傳遞給它的 jmp_buf 引數。完成時,setjmp 將返回 0。下一步是呼叫可能導致異常情況的函式(List_InsertAfterNth)。如果一切順利,控制權將返回到函式呼叫後的語句,在本例中是返回語句。否則,控制權將返回到發出 setjmp 的地方,並將 List_InsertAfterNth 中呼叫 longjmp 返回的值作為呼叫 setjmp 的結果。

Passing context information

我們可以總結一下發生了什麼:

  • 透過呼叫 setjmp 記錄機器狀態並返回 0。
  • 呼叫可能導致異常情況的函式。
  • 如果一切正常,則透過 return 語句從函式返回有效值。控制流程將像沒有進行特殊安排一樣進行。否則,透過 longjmp 函式從函式返回。這樣做將(除了展開執行時堆疊)將控制權返回到呼叫 setjmp 函式的地方。假裝 setjmp 之前沒有被呼叫過,並將其結果作為 longjmp 函式的第二個引數中返回的值。
  if (setjmp(ins_a_Nth) == EXC_LIST_NOSUCHITEM)
    longjmp(iafc, EXC_LIST_NOSUCHITEM);
  List_InsertAfterNth(this, new_item, after, 1, ins_a_Nth);
} /* end of void List_InsertAfterFirst(const List, Object, Object, jmp_buf) */

void List_InsertAfterLast(const List this, Object new_item, Object aft, jmp_buf ialc)
  /* throws NoSuchItem */ {
  jmp_buf li_a, li_n, li_p;
  ListIterator itr = List_ListIterator(this, ITERATOR_BACKWARD);

  for(; ListIterator_HasPrevious(itr); ) 
    if (this->compare_component(aft, ListIterator_Previous(itr, li_p)) == 0) {
      ListIterator_Next(itr, li_n);
      ListIterator_Add(itr, new_item, li_a);
      free(itr);
      return;
    } /* end of if(this->compare_component(aft, …) == 0) */

  free(itr);
  longjmp(ialc, EXC_LIST_NOSUCHITEM);
} /* end of void List_InsertAfterLast(const List, Object, Object, jmp_buf) */

void List_InsertAfterNth(const List this, Object new_item, Object aft, int n, jmp_buf iaNc)
  /* throws NoSuchItem */ {
  int index = 0;
  jmp_buf li_a, li_n;
  ListIterator itr = List_ListIterator(this, ITERATOR_FORWARD);

  for(; index < n && ListIterator_HasNext(itr); )
    if (this->compare_component(aft, ListIterator_Next(itr, li_n)) == 0)
      index++;
  if (index < n) { 
    free(itr);
    longjmp(iaNc, EXC_LIST_NOSUCHITEM);
  } /* end of if (index < n) */
  ListIterator_Add(itr, new_item, li_a);
  free(itr);
} /* end of void List_InsertAfterNth(const List, Object, Object, int, jmp_buf) */

void List_InsertBeforeFirst(const List this, Object new_item, Object before, jmp_buf ibfc)
  /* throws NoSuchItem */ {
  jmp_buf ins_b_Nth;

  if (setjmp(ins_b_Nth) == EXC_LIST_NOSUCHITEM)
  longjmp(ibfc, EXC_LIST_NOSUCHITEM);
  List_InsertBeforeNth(this, new_item, before, 1, ins_b_Nth);
} /* end of void List_InsertBeforeFirst(const List, Object, Object, jmp_buf) */

void List_InsertBeforeLast(const List this, Object new_item, Object bef, jmp_buf iblc)
  /* throws NoSuchItem */ {
  jmp_buf li_a, li_p;
  ListIterator itr = List_ListIterator(this, ITERATOR_BACKWARD);

  for(; ListIterator_HasPrevious(itr); ) 
    if (this->compare_component(bef, ListIterator_Previous(itr, li_p)) == 0) {
      ListIterator_Add(iterator, new_item, li_a);
      free(itr);
      return;
    } /* end of if (this->compare_component…) */

  free(itr);
  longjmp(iblc, EXC_LIST_NOSUCHITEM);
} /* end of void List_InsertBeforeLast(const List, Object, Object, jmp_buf) */

void List_InsertBeforeNth(const List this, Object new_item, Object before, int n, jmp_buf ibNc)
  /* throws NoSuchItem */ {
  int index = 0;
  jmp_buf li_a, li_n, li_p;
  ListIterator itr = List_ListIterator(this, ITERATOR_FORWARD);

  for(; index < n && ListIterator_HasNext(itr); )
    if (this->compare_component(before, ListIterator_Next(itr, li_n)) == 0) 
      index++;

  if (index < n) { 
    free(itr); 
    longjmp(ibNc, EXC_LIST_NOSUCHITEM);
  } /* end of if (index < n) */

  ListIterator_Previous(itr, li_p);
  ListIterator_Add(itr, new_item, li_a);
  free(itr);
} /* end of void List_InsertBeforeNth(const List, Object, Object, int, jmp_buf) */

void List_InsertInAscendingOrder(const List this, Object new_item) {
  jmp_buf li_a, li_n, li_p;
  ListIterator itr = List_ListIterator(this, ITERATOR_BACKWARD);

  for (; ListIterator_HasPrevious(itr); ) {
    Object next_item = ListIterator_Previous(itr, li_p);
    if (this->compare_component(new_item, next_item) < 0) continue;
    ListIterator_Next(itr, li_n);
    ListIterator_Add(itr, new_item, li_a);
    free(itr);
    return;
  } /* end of for (; ListIterator_HasPrevious(itr); ) */
  ListIterator_Add(itr, new_item, li_a);
  free(itr);
} /* end of void List_InsertInAscendingOrder(const List, Object) */

void List_InsertInAscendingOrderEx(const List this, Object new_item, COMPARISON_FUNC cmp) {
  jmp_buf li_a, li_n, li_p;
  ListIterator itr = List_ListIterator(this, ITERATOR_BACKWARD);

  for (; ListIterator_HasPrevious(itr); ) {
    Object next_item = ListIterator_Previous(itr, li_p);
    if (cmp(new_item, next_item) < 0) continue;
    ListIterator_Next(itr, li_n);
    ListIterator_Add(itr, new_item, li_a);
    free(itr);
    return;
  } /* end of for (; ListIterator_HasPrevious(itr); )*/
  ListIterator_Add(itr, new_item, li_a);
  free(itr);
} /* end of void List_InsertInAscendingOrderEx(const List, Object, COMPARISON_FUNC) */

void List_InsertInDescendingOrder(const List this, Object new_item) {
  jmp_buf li_a, li_n, li_p;
  ListIterator itr = List_ListIterator(this, ITERATOR_BACKWARD);

  for (; ListIterator_HasPrevious(itr); ) {
    Object next_item = ListIterator_Previous(itr, li_p);
    if (this->compare_component(new_item, next_item) > 0) continue;
    ListIterator_Next(itr, li_n);
    ListIterator_Add(itr, new_item, li_a);
    free(itr);
    return;
  } /* end of for (; ListIterator_HasPrevious(itr); )*/
  ListIterator_Add(itr, new_item, li_a);
  free(itr);
} /* end of void List_InsertInDescendingOrder(const List, Object) */

void List_InsertInDescendingOrderEx(const List this, Object new_item, COMPARISON_FUNC cmp) {
  jmp_buf li_a, li_n, li_p;
  ListIterator itr = List_ListIterator(this, ITERATOR_BACKWARD);

  for (; ListIterator_HasPrevious(itr); ) {
    Object next_item = ListIterator_Previous(itr, li_p);
    if (cmp(new_item, next_item) > 0) continue;
    ListIterator_Next(itr, li_n);
    ListIterator_Add(itr, new_item, li_a);
    free(itr);
    return;
  } /* end of for (; ListIterator_HasPrevious(itr); )*/
  ListIterator_Add(itr, new_item, li_a);
  free(itr);
} /* end of void List_InsertInDescendingOrderEx(const List, Object, COMPARISON_FUNC) */

int List_RemoveAll(const List this, Object item, jmp_buf rac) /* throws ListEmpty */ {
  int i = 0;
  jmp_buf li_n, li_r;
  ListIterator itr;

  if (List_IsEmpty(this)) longjmp(rac, EXC_LIST_EMPTY);

  for (itr = List_ListIterator(this, ITERATOR_FORWARD);
    ListIterator_HasNext(itr); )
  if (this->compare_component(item, ListIterator_Next(itr, li_n)) == 0) {
    ListIterator_Remove(itr, li_r);
    i++;
  } /* end of if(->compare_component(item, …) == 0) */
  free(itr);

  return i;
} /* end of int List_RemoveAll(const List, Object, jmp_buf) */

void List_RemoveFirst(const List this, Object new_item, jmp_buf rfc) /* throws ListEmpty, NoSuchItem */ {
  jmp_buf rmv_Nth;

  switch (setjmp(rmv_Nth)) {
    case 0: break;
    case EXC_LIST_EMPTY: longjmp(rfc, EXC_LIST_EMPTY);
    case EXC_LIST_NOSUCHITEM: longjmp(rfc, EXC_LIST_NOSUCHITEM);
  } /* end of switch(setjmp(rmv_Nth)) */
  List_RemoveNthFromFront(this, new_item, 1, rmv_Nth);
} /* end of void List_RemoveFirst(const List, Object, jmp_buf) */

void List_RemoveLast(const List this, Object new_item, jmp_buf rlc) /* throws ListEmpty, NoSuchItem */ {
  jmp_buf rmv_Nth;
  int status = setjmp(rmv_Nth);

  switch (status) {
    case 0: break;
    case EXC_LIST_EMPTY: longjmp(rlc, EXC_LIST_EMPTY);
    case EXC_LIST_NOSUCHITEM: longjmp(rlc, EXC_LIST_NOSUCHITEM);
  } /*  end of switch(status) */
  List_RemoveNthFromEnd(this, new_item, 1, rmv_Nth);
} /* end of void List_RemoveLast(const List, Object, jmp_buf) */

void List_RemoveNthFromEnd(const List this, Object new_itm, int n, jmp_buf rNec)
  /* throws ListEmpty, NoSuchItem */ {
  int i = 0;
  jmp_buf li_p, li_r;
  ListIterator itr;

  if (List_IsEmpty(this)) longjmp(rNec, EXC_LIST_EMPTY);

  for (itr = List_ListIterator(this, ITERATOR_BACKWARD); ListIterator_HasPrevious(itr) && i < n; )
    if (this->compare_component(new_itm, ListIterator_Previous(itr, li_p)) == 0) 
  i++;

  if (i == n) {
    ListIterator_Remove(itr, li_r);
    free(itr);
  } else { 
      free(itr);
      longjmp(rNec, EXC_LIST_NOSUCHITEM);
    } /* end of else */
} /* end of void List_RemoveNthFromEnd(const List, Object, int, jmp_buf) */

void List_RemoveNthFromFront(const List this, Object new_itm, int n, jmp_buf rNfc) 
  /* throws ListEmpty, NoSuchItem */ {
  int i = 0;
  jmp_buf li_n, li_r;
  ListIterator itr;

  if (List_IsEmpty(this)) longjmp(rNfc, EXC_LIST_EMPTY);

  for (itr = List_ListIterator(this, ITERATOR_FORWARD); ListIterator_HasNext(itr) && i < n; )
    if (this->compare_component(new_itm, ListIterator_Next(itr, li_n)) == 0)
  i++;

  if (i == n) {
    ListIterator_Remove(itr, li_r);
    free(itr);
  } else { 
      free(itr);
      longjmp(rNfc, EXC_LIST_NOSUCHITEM);
    } /* end of else */
} /* end of void List_RemoveNthFromFront(const List, Object, int, jmp_buf) */

void ListIterator_Destroy(ListIterator* this) {
  if (*this == NULL) return;

  (*this)->underlying_list = NULL;
  free(*this);
  *this = NULL;
} /* end of void ListIterator_Destroy(ListIterator) */

BOOL ListIterator_HasNext(const ListIterator this) {
  if (this->index == this->underlying_list->size) return FALSE;
    else return TRUE;
} /* end of BOOL ListIterator_HasNext(const ListIterator) */

Object ListIterator_Next(const ListIterator this, jmp_buf li_nc) /* throws NoSuchElement */ {
  Object retVal;

  if (!ListIterator_HasNext(this))
    longjmp(li_nc, EXC_ITERATOR_NOSUCHELEMENT);

  retVal = this->ptr->info;
  this->ptr = this->ptr->next;
  this->index++;
  this->last_op = IT_OPERATION_NEXT;

  return retVal;
} /* end of Object ListIterator_Next(const ListIterator, jmp_buf) */ 

int ListIterator_NextIndex(const ListIterator this) {
  return(this->index);
} /* end of int ListIterator_NextIndex(const ListIterator) */

BOOL ListIterator_HasPrevious(const ListIterator this) {
  if (this->index == 0) return FALSE;
    else return TRUE;
} /* end of BOOL ListIterator_HasPrevious(const ListIterator) */

Object ListIterator_Previous(const ListIterator this, jmp_buf li_pc) /* throws NoSuchElement */ {
  if (!ListIterator_HasPrevious(this)) 
    longjmp(li_pc,EXC_ITERATOR_NOSUCHELEMENT);

  this->ptr = this->ptr->prev;
  this->index--;
  this->last_op = IT_OPERATION_PREVIOUS;

  return this->ptr->info;
} /* end of BOOL ListIterator_Previous(const ListIterator, jmp_buf) */

int ListIterator_PreviousIndex(const ListIterator this) {
  return (this->index - 1);
} /* end of int ListIterator_PreviousIndex(const ListIterator) */

void ListIterator_Remove(const ListIterator this, jmp_buf li_rc) 
  /* throws IllegalStateException, UnsupportedOperationException */ {
  int index; /* index of the item to be deleted */
  _List to_be_deleted;

  if (this->last_op != IT_OPERATION_NEXT && this->last_op != IT_OPERATION_PREVIOUS)
    longjmp(li_rc, EXC_ITERATOR_ILLEGALSTATE);

  switch (this->last_op) {
    case IT_OPERATION_NEXT:
      to_be_deleted = this->ptr->prev;
      index = this->index - 1;
      break;
    case IT_OPERATION_PREVIOUS:
      to_be_deleted = this->ptr;
      this->ptr = this->ptr->next;
      index = this->index++;
      break;
  } /* end of switch(this->lastOp) */

  this->last_op = IT_OPERATION_REMOVE;
  if (index + 1 == this->underlying_list->size) /* if it is the last item in the list */ {
    jmp_buf rmv_e_c;
    if (setjmp(rmv_e_c) != 0) NO_OP;
    List_RemoveFromEnd(this->underlying_list, rmv_e_c);
  }
  else if (index == 0) /* if it is the first item in the list */ {
      jmp_buf rmv_f_c;
      if (setjmp(rmv_f_c) != 0) NO_OP;
      List_RemoveFromFront(this->underlying_list, rmv_f_c);
  } else {
      _List next_list = to_be_deleted->next;
      _List prev_list = to_be_deleted->prev;
      prev_list->next = next_list; next_list->prev = prev_list;
      this->underlying_list->destroy_component(to_be_deleted->info);
      this->underlying_list->size--;
      to_be_deleted->prev = to_be_deleted->next = NULL;
      to_be_deleted->info = NULL;
      free(to_be_deleted);
    }

  this->index--;
} /* end of void ListIterator_Remove(const ListIterator, jmp_buf) */

void ListIterator_Add(const ListIterator this, Object new_item, jmp_buf li_ac) 
  /* throws IllegalState, UnsupportedOperation */ {
  _List new_sublist;

  if (!ListIterator_HasNext(this)) 
    List_InsertAtEnd(this->underlying_list, new_item);
  else if (!ListIterator_HasPrevious(this))
    List_InsertInFront(this->underlying_list, new_item);
    else {
      new_sublist = create_sublist(new_item);
      new_sublist->next = this->ptr;
      new_sublist->prev = this->ptr->prev;
      this->ptr->prev->next = new_sublist;
      this->ptr->prev = new_sublist;
      this->underlying_list->size++;
    }

  this->index++;
  this->last_op = IT_OPERATION_ADD;
} /* end of void ListIterator_Add(const ListIterator, Object) */

void ListIterator_Set(const ListIterator this, Object new_value, jmp_buf li_sc)
  /* throws IllegalArgument, IllegalState, UnsupportedOperation */ {
  if (this->last_op == IT_OPERATION_NEXT) { 
    this->underlying_list->destroy_component(this->ptr->prev->info); 
    this->ptr->prev->info = new_value;
  }
  else if (this->last_op == IT_OPERATION_PREVIOUS) {
    this->underlying_list->destroy_component(this->ptr->info);
    this->ptr->info = new_value;
  } else longjmp(li_sc, EXC_ITERATOR_ILLEGALSTATE);

  this->last_op = IT_OPERATION_SET;
} /* end of void ListIterator_Set(const ListIterator, Object, jmp_buf) */

程式的健壯性和正確性

[編輯 | 編輯原始碼]

在我們的測試程式中,我們提供了兩個補充異常概念的簡單示例:斷言機制和訊號。前者用於讓編譯器在程式碼中插入執行時控制,因此可以被視為構建正確軟體的工具。後者是通知程序已發生事件的通知。換句話說,訊號是軟體中斷——由於計算機、作業系統或系統中某個程序中的某些意外情況——傳遞給程序。

斷言機制和異常處理服務於類似的目的;它們不服務於相同目的。儘管這兩種機制都提高了可靠性,但它們透過解決不同的方面來做到這一點:健壯性和正確性。異常處理透過提供從意外情況恢復的能力來實現更健壯的系統,而斷言透過檢查開發人員做出的斷言的有效性來幫助確保正確性。

定義:健壯性與系統對各種情況和可能意外情況的合理反應能力有關。正確性與系統對規範的遵守程度有關,該規範說明了對系統預期行為的要求。

例如,磁碟上缺少資料檔案與實現的正確性無關。程式設計師最好提供一些處理此異常情況的程式碼。另一方面,傳遞給子程式的非法引數值很可能是先前錯誤的結果,例如程式程式碼中草率的範圍檢查。

訊號在不同的意義上補充異常:當異常是由於當前程式活動[因此是程式語言概念,可以說它是內部的]的意外結果時,訊號——作業系統概念——通常是外部的,可能是由於硬體、作業系統或程序造成的。

可以透過一個例子更好地理解異常和訊號之間的關係。考慮整數除法。如果除數恰好為零,處理器將生成一個除零中斷,該中斷將由作業系統進一步傳遞給語言執行時,作為SIGFPE訊號。此訊號最終由語言執行時轉換為異常,例如ArithmeticException,並傳遞迴包含違規程式碼的程式,希望該程式能夠在該程式中捕獲和處理。

訊號可以分為三類:程式錯誤外部事件顯式請求。錯誤表示程式執行了某些無效操作,並且作業系統或計算機已檢測到該操作。這包括除零、取消引用空指標、取消引用未初始化指標等等。外部事件與 I/O 或與其他程序的通訊有關。此類別的示例包括計時器的到期、子程序的終止、停止或掛起程序、將使用者終端讓給後臺作業進行輸入或輸出等等。顯式請求是專門生成訊號的庫函式呼叫,例如 abort 和 kill 函式,它們基本上分別生成SIGABRTSIGKILL

訊號示例包括

SIGINT (中斷)
收到特殊中斷鍵(通常為 Ctrl-C)後,將向程序傳送SIGINT訊號。
SIGKILL
此中斷會立即終止程序。無法阻止、處理或忽略此中斷。此訊號和上一個訊號的典型用法是終止已進入無限迴圈的程式或佔用系統資源過多的程式。
SIGTERM
SIGKILL類似,此中斷會導致程式終止。但是,與SIGKILL不同,可以阻止、處理或忽略此中斷。
SIGSEGV (段錯誤)
程式正在嘗試讀取/寫入未為其分配的記憶體。當使用超出界限的索引值使用陣列或取消引用具有正確對齊初始值的未初始化指標時,可能會生成此訊號。
SIGILL (非法指令)
遇到非法或特權指令。由於執行旨在用於不同處理器的二進位制檔案,可能會引發此類訊號,這可以透過在 i386 上執行包含 Pentium-4 指令的可執行檔案來舉例說明。另一個原因是嘗試執行損壞的可執行檔案。最後,嘗試在使用者程式中執行特權指令(例如操縱系統表的指令)也會導致生成此訊號。
SIGBUS
嘗試訪問無效地址。這可能是由於嘗試訪問未對齊的資料造成的,這可能發生在取消引用具有隨機值的未初始化指標時。
SIGFPE
發生了通用算術異常。請注意,這不僅限於浮點數,正如其名稱可能暗示的那樣。它可以是整數除零、溢位等等。
SIGALRM
每當先前設定的計時器到期時,都會生成此訊號。
SIGUSR1SIGUSR2
為程式設計師預留,這些訊號可以根據需要進行調整。

當訊號發出(或生成)時,它將變為待處理,這意味著它正在等待傳遞給程序。如果未被程序阻止,這通常需要很短的時間,並且在傳遞時,將執行某些例程來處理訊號。這稱為“處理訊號”,可能採取的形式是簡單地忽略它、接受系統提供的預設操作,或執行使用者指定的操作。如果訊號被阻止,這意味著它的處理被無限期地推遲到以後的時間,它將保持在(阻塞的)待處理狀態。此訊號可以稍後解除阻止,由程序處理。

State transition diagram of a signal

需要注意的是,某些訊號既不能被阻止也不能被忽略。事實上,一些訊號(例如 SIGKILL)甚至不能使用使用者指定的動作來處理。

測試程式

[編輯 | 編輯原始碼]
List_Test.c
#include <assert.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

#include "General.h"
#include "Wrappers.h"
#include "ds/ListExtended.h"
#include "ds/ListIterator.h"

void write_to_screen(const List this) {
  jmp_buf nxt_c;
  ListIterator itr = List_ListIterator(this, ITERATOR_FORWARD);

  printf("<%i> <head: ", List_Size(this));
  for (; ListIterator_HasNext(itr); ) {
    printf("%i", Integer_Value((Integer) ListIterator_Next(itr, nxt_c)));
    if (ListIterator_HasNext(itr)) printf(", ");
  } /* end of for(; ListIterator_HasNext(itr);) */
  printf(" :tail>\n");
} /* end of void write_to_screen(const List) */

void test_extended_removal(const List l) {
  int n;
  jmp_buf rac, rfc, rlc, rNec, rNfc;

  printf("\nTESTING [EXTENDED] REMOVE OPERATIONS\n");
  List_RemoveLast(l, Integer_Create(8), rlc);
  printf("Removing the last 8...\n"); write_to_screen(l);

  List_RemoveFirst(l, Integer_Create(5), rfc);
  printf("Removing the first 5...\n"); write_to_screen(l);

  List_RemoveNthFromEnd(l, Integer_Create(7), 3, rNfc);
  printf("Removing the third 7 from the end...\n"); write_to_screen(l);

  List_RemoveNthFromFront(l, Integer_Create(5), 2, rNfc);
  printf("Removing the second 5...\n"); write_to_screen(l);

  n = List_RemoveAll(l, Integer_Create(6), rac);
  printf("Removing all 6's...%i 6's removed.\n", n); write_to_screen(l);

  n = List_RemoveAll(l, Integer_Create(7), rac);
  printf("Removing all 7's...%i 7's removed.\n", n); write_to_screen(l);
} /* end of void test_extended_removal(const List) */

void test_extended_insertion(const List l) {
  jmp_buf iafc, ialc, ibfc, iblc, iaNc, ibNc;

  printf("\nTESTING [EXTENDED]INSERT OPERATIONS\n");
  List_InsertBeforeFirst(l, Integer_Create(5), Integer_Create(6), ibfc);
  printf("Inserting 5 before first 6...\n"); write_to_screen(l);

  List_InsertAfterFirst(l, Integer_Create(5), Integer_Create(6), iafc);
  printf("Inserting 5 after first 6...\n"); write_to_screen(l);

  List_InsertBeforeLast(l, Integer_Create(6), Integer_Create(5), iblc);
  printf("Inserting 6 before last 5...\n"); write_to_screen(l);

  List_InsertAfterLast(l, Integer_Create(7), Integer_Create(6), ialc);
  printf("Inserting 7 after last 6...\n"); write_to_screen(l);

  List_InsertAfterLast(l, Integer_Create(7), Integer_Create(36), ialc);
  printf("Inserting 7 after last 36...\n"); write_to_screen(l);

  List_InsertAfterLast(l, Integer_Create(8), Integer_Create(7), ialc);
  printf("Inserting 8 after last 7...\n"); write_to_screen(l);

  List_InsertAfterNth(l, Integer_Create(6), Integer_Create(5), 2, iaNc);
  printf("Inserting 6 after the second 5...\n"); write_to_screen(l);

  List_InsertBeforeNth(l, Integer_Create(6), Integer_Create(5), 3, ibNc);
  printf("Inserting 6 before the third 5...\n"); write_to_screen(l);
} /* end of void test_extended_insertion(const List) */

void test_iterators(const List l) {
  ListIterator itr;
  jmp_buf li_nc, li_pc;

  printf("\nTESTING THE FORWARD ITERATOR\n");
  itr = List_ListIterator(l, ITERATOR_FORWARD);
  for (; ListIterator_HasNext(itr); )
    printf("%i ", Integer_Value((Integer) ListIterator_Next(itr, li_nc)));
  ListIterator_Destroy(&itr);
  printf("\n");

  printf("\nTESTING THE BACKWARD ITERATOR\n");
  itr = List_ListIterator(l, ITERATOR_BACKWARD);
  for (; ListIterator_HasPrevious(itr); )
    printf("%i ", Integer_Value((Integer) ListIterator_Previous(itr, li_pc)));
  ListIterator_Destroy(&itr);
  printf("\n");
} /* end of test_iterators(const List) */

void test_empty_list(const List l) {
  jmp_buf gfc, glc, rac, rec, rfc, tac;

  assert(List_IsEmpty(l));
  printf("Working on the following [empty!] list...\n");
  write_to_screen(l);

  printf("Trying to get the first item...");
  switch(setjmp(gfc)) {
    case 0: List_GetFirst(l, gfc); break;
    case EXC_LIST_EMPTY: printf("List is empty!!!\n"); break;
  } /* end of switch(setjmp(gfc)) */

  printf("Trying to get the last item...");
  switch(setjmp(glc)) {
    case 0: List_GetLast(l, glc); break;
    case EXC_LIST_EMPTY: printf("List is empty!!!\n"); break;
  } /* end of switch(setjmp(glc)) */

  printf("Trying to remove the first [head] item...");
  switch(setjmp(rfc)) {
    case 0: List_RemoveFromFront(l, rfc); break;
    case EXC_LIST_EMPTY: printf("List is empty!!!\n"); break;
  } /* end of switch(setjmp(rfc)) */

  printf("Trying to remove the last [tail] item...");
  switch(setjmp(rec)){
    case 0: List_RemoveFromEnd(l, rec); break;
    case EXC_LIST_EMPTY: printf("List is empty!!!\n"); break;
  } /* end of switch(setjmp(rec)) */

  printf("Trying to convert to array...");
  switch(setjmp(tac)) {
    case 0: List_ToArray(l, tac); break;
    case EXC_LIST_EMPTY: printf("List is empty!!!\n"); break;
  } /* end of switch(setjmp(tac)) */

  printf("Trying to remove all 18's...");
  switch(setjmp(rac)) {
    case 0: List_RemoveAll(l, Integer_Create(18), rac); break;
    case EXC_LIST_EMPTY: printf("List is empty!!!\n"); break;
  } /* end of switch(setjmp(rac)) */
  printf("\n");
} /* end of void test_empty_list(const List) */

void test_nonempty_list(const List l) {
  int n;
  jmp_buf iafc, ialc, iaNc, ibfc, iblc, ibNc;
  jmp_buf rac, rfc, rlc, rNec, rNfc;

  printf("Working on the following list...\n"); write_to_screen(l);

  printf("Trying to insert 5 after first 18...");
  switch(setjmp(iafc)) {
    case 0:
      List_InsertAfterFirst(l, Integer_Create(5), Integer_Create(18), iafc); break;
    case EXC_LIST_NOSUCHITEM: printf("No such item!!!\n");
  } /* end of switch(setjmp(iafc)) */

  printf("Trying to insert 5 after last 18...");
  switch(setjmp(ialc)) {
    case 0:
      List_InsertAfterLast(l, Integer_Create(5), Integer_Create(18), ialc); break;
    case EXC_LIST_NOSUCHITEM: printf("No such item!!!\n");
  } /* end of switch(setjmp(ialc)) */

  printf("Trying to insert 5 after the third 18...");
  switch(setjmp(iaNc)) {
    case 0:
      List_InsertAfterNth(l, Integer_Create(5), Integer_Create(18), 3, iaNc); break;
    case EXC_LIST_NOSUCHITEM: printf("No such item!!!\n");
  } /* end of switch(setjmp(iaNc)) */

  printf("Trying to insert 5 before first 18...");
  switch(setjmp(ibfc)) {
    case 0:
      List_InsertBeforeFirst(l, Integer_Create(5), Integer_Create(18), ibfc); break;
    case EXC_LIST_NOSUCHITEM: printf("No such item!!!\n");
  } /* end of switch(setjmp(ibfc)) */

  printf("Trying to insert 5 before last 18...");
  switch(setjmp(iblc)) {
    case 0:
      List_InsertBeforeLast(l, Integer_Create(5), Integer_Create(18), iblc); break;
    case EXC_LIST_NOSUCHITEM: printf("No such item!!!\n");
  } /* end of switch(setjmp(iblc)) */

  printf("Trying to insert 5 before the third 18...");
  switch(setjmp(ibNc)) {
    case 0:
      List_InsertBeforeNth(l, Integer_Create(5), Integer_Create(18), 3, ibNc); break;
    case EXC_LIST_NOSUCHITEM: printf("No such item!!!\n");
  } /* end of switch(setjmp(ibNc)) */

  printf("Trying to remove last 18...");
  switch(setjmp(rlc)) {
    case 0: List_RemoveLast(l, Integer_Create(18), rlc); break;
    case EXC_LIST_EMPTY: printf("List is empty!!!\n"); break;
    case EXC_LIST_NOSUCHITEM: printf("No such item!!!\n");
  } /* end of switch(setjmp(rlc)) */

  printf("Trying to remove first 18...");
  switch(setjmp(rfc)) {
    case 0: List_RemoveFirst(l, Integer_Create(18), rfc); break;
    case EXC_LIST_EMPTY: printf("List is empty!!!\n"); break;
    case EXC_LIST_NOSUCHITEM: printf("No such item!!!\n");
  } /* end of switch(setjmp(rfc)) */

  printf("Trying to remove third 6...");
  switch(setjmp(rNfc)) {
    case 0:
      List_RemoveNthFromFront(l, Integer_Create(6), 3, rNfc); break;
    case EXC_LIST_EMPTY: printf("List is empty!!!\n"); break;
    case EXC_LIST_NOSUCHITEM: printf("No such item!!!\n");
  } /* end of switch(setjmp(rNfc)) */

  printf("Trying to remove third 6 from end...");
  switch(setjmp(rNec)) {
    case 0:
      List_RemoveNthFromEnd(l, Integer_Create(6), 3, rNec); break;
    case EXC_LIST_EMPTY: printf("List is empty!!!\n"); break;
    case EXC_LIST_NOSUCHITEM: printf("No such item!!!\n");
  } /* end of switch(setjmp(rNec)) */

  printf("Trying to remove all 49's...");
  switch(setjmp(rac)) {
    case 0:
      n = List_RemoveAll(l, Integer_Create(49), rac); 
      assert(List_RemoveAll(l, Integer_Create(49), rac) == 0);
      break;
    case EXC_LIST_EMPTY: printf("List is empty!!!\n");
  } /* end of switch(setjmp(rac)) */
  printf("%i 49's removed.\n", n);
} /* void test_nonempty_list(const List) */

void unexpected_cond(int signal_no) {
  if (signal_no != SIGUSR1) 
    fprintf(stderr, "Unexpected signal: %d\n", signal_no);
    else fprintf(stderr, "User signal(SIGUSR1)!\n");

  exit(signal_no);
} /* void unexpected_cond(int) */

int main(void) {
  int i = 0;
  jmp_buf gfc, glc, lac, rec, rfc;
  List_Component_Funcs int_funcs = 
    { &Integer_CompareIntegers, &Integer_CopyInteger, &Integer_DestroyInteger };
  Object obj_array[] =
    { Integer_Create(16), Integer_Create(25), Integer_Create(36), Integer_Create(49) };
  Integer* corr_array;
  List l1 = List_Create(DEFAULT, int_funcs);
  List empty_list = List_Create(COPY, l1);
  List l2 = List_Create(FROM_ARRAY, int_funcs, obj_array, 4);
  List merged_list;

下一行程式碼對我們的程式做出了一個斷言:l2,它之前是從四個元素的陣列中建立的,大小為四個。否則將意味著建構函式或List_Size函式中存在語義錯誤,必須在List模組上市之前進行糾正。

如果程式狀態assert證明我們的斷言為假,則會導致中止,並向標準輸出寫入診斷訊息,其中包括斷言的“字串化”版本以及檔名和行號。

示例:在 C 中實現一種方案,確保不會默默地忽略越界索引值。

... void f(int *iarr, unsigned int size) { ... assert(i * j + k < size && i * j + k >= 0); ... iarr[i * j + k] ... ... } /* end of void f(int*, unsigned int) */ ... f(a, 10); ... f(b, 25); ... ...

當然,這也有代價:每次看到斷言,都會花費額外的時間來驗證斷言,這意味著充滿 assert 的程式碼執行速度會更慢。考慮到生產質量程式碼不僅需要無錯誤,而且需要快速,在最終專案中使用 assert 就沒有意義了。那麼怎麼辦?我們是否應該移除所有斷言(可能會有幾十個)——只是為了在需要維護時再放回去?其實不是!在我們的檔案中或命令列中定義 NDEBUG 宏作為編譯時開關,可以解決這個問題。

  assert(List_Size(l2) == 4);

  for (; i < 10; i +=2) List_InsertInFront(l1, Integer_Create(i));
  for (i = 1; i < 10; i +=2) List_InsertAtEnd(l1, Integer_Create(i));
  printf("First list: "); write_to_screen(l1);
  printf("Second List: "); write_to_screen(l2);

  printf("Merging the lists...\n");
  merged_list = List_Merge(l1, l2); 
  printf("Merged List: "); write_to_screen(merged_list);

另一種處理意外情況的機制:訊號。訊號最初是 UNIX 的概念,用於通知程序(即正在執行的程式)關於 [通常] 非同步事件的發生,例如無效的記憶體訪問或嘗試除以零。

除了由計算機的錯誤檢測機制或程式外部的操作觸發訊號之外,還可以使用 raise 函式顯式地“引發”訊號。所有這些都會導致相關訊號傳送到程序。[10] 一旦訊號被引發(或以某種方式觸發),它需要被處理。這是透過呼叫訊號的處理程式完成的。[11]

這一切都很好,但是我們如何更改程式對特定訊號的反應?畢竟,對於同一訊號的發生,我們有時可以修改環境並讓程式繼續執行,而其他時候我們必須簡單地終止程式。我們可以透過更改處理程式函式來實現這一點,我們使用 signal 函式來實現。將新的處理程式與訊號關聯起來,此函式將返回指向先前處理程式的指標。[12] 從現在開始,除非對 signal 的另一次呼叫再次修改處理程式,否則所有相關訊號最終都將在此新函式中處理。

在建立處理程式時,可以將兩個特殊值作為第二個引數傳遞給 signalSIG_IGNSIG_DFL。前者表示第一個引數中傳遞的訊號將被忽略,而後者表示該訊號將接收其預設處理。

  if (List_Size(merged_list) != List_Size(l1) + List_Size(l2)) {
    void (*previous_handler)(int) = signal(SIGUSR1, &unexpected_cond);    
    raise(SIGUSR1);
    signal(SIGUSR1, previous_handler);
  } /* end of if(List_Size(merged_list) != List_Size(l1) + List_Size(l2)) */

  printf("First item of the merged list: %i\n", Integer_Value((Integer)List_GetFirst(merged_list, gfc)));
  printf("Last item of the merged list: %i\n", Integer_Value((Integer)List_GetLast(merged_list, glc)));
  printf("Removing the first and last items in the merged list...\n");
  List_RemoveFromEnd(merged_list, rec);
  List_RemoveFromFront(merged_list, rfc);

  write_to_screen(merged_list);

  corr_array = (Integer *) List_ToArray(merged_list, lac);
  printf("Corresponding array...\n");
  for (i = 0; i < List_Size(merged_list); i++)
    printf("[%i] %i ", i, Integer_Value(corr_array[i]));
  printf("\n");

  test_extended_insertion(merged_list);
  test_extended_removal(merged_list);
  test_iterators(merged_list);

  printf("\nTESTING EXCEPTIONAL CONDITIONS\n");
  test_empty_list(empty_list);
  test_nonempty_list(merged_list);

  return 0;
} /* end of int main(void) */
gcc –c List.c –ID:/include↵ # 在 Cygwin 中

為了在測試階段建立可執行檔案,我們應該使用以下命令。這將有效地啟用斷言功能。

gcc –o Test.exe List_Test.c List.o –lWrappers –ID:/include –LD:/library↵

擺脫程式中的語義錯誤(或至少希望如此),我們可以停用斷言功能。這是透過定義 NDEBUG 宏來完成的。

gcc –DNDEBUG –o Test.exe List_Test.c List.o –lWrappers –ID:/include –LD:/library↵

透過組合實現程式碼重用

[edit | edit source]

介面

[edit | edit source]
Stack.h
#ifndef STACK_H
#define STACK_H

#include <setjmp.h>

#include "General.h"

#define EXC_STACK_EMPTY -41

struct STACK;
typedef struct STACK* Stack;

typedef struct _Obj_funcs {
  COMPARISON_FUNC compare_component;
  COPY_FUNC copy_component;
  DESTRUCTION_FUNC destroy_component;
} Stack_Component_Funcs;

extern Stack Stack_Create(int constructor_type, ...);
extern void Stack_Destroy(Stack*);

extern Object Stack_Peek(const Stack, jmp_buf spc) /* throws StackEmpty */ ;
extern Object Stack_Pop(const Stack, jmp_buf spc) /* throws StackEmpty */ ;
extern void Stack_Push(const Stack, Object);
extern BOOL Stack_IsEmpty(const Stack);

#endif

實現

[edit | edit source]
Stack.c
#include <setjmp.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

#include "General.h"
#include "ds/List.h"
#include "ds/Stack.h"

struct STACK { List underlying_list; };

Stack Stack_Create(int type, ...) {
  va_list ap;
  List_Component_Funcs lst_funcs;
  Stack_Component_Funcs funcs;
  Stack existing_stack;
  Stack ret_stack = (Stack) malloc(sizeof(struct STACK));

  if (!ret_stack) {
    fprintf(stderr, "Out of memory...\n");
    return(NULL);
  } /* end of if(!ret_stack) */

  va_start(ap, type);
  switch(type) {
    case COPY:
      existing_stack = va_arg(ap, Stack);
      ret_stack->underlying_list = List_Create(COPY, existing_stack->underlying_list);
      break;
    case DEFAULT:
      funcs = va_arg(ap, Stack_Component_Funcs);
      lst_funcs.compare_component = funcs.compare_component;
      lst_funcs.copy_component = funcs.copy_component;
      lst_funcs.destroy_component = funcs.destroy_component;
      ret_stack->underlying_list = List_Create(DEFAULT, lst_funcs);
      break;
  } /* end of switch(type) */
  va_end(ap);

  return ret_stack;
} /* end of Stack Stack_Create(int, ...) */

void Stack_Destroy(Stack* this) {
  if (*this == NULL) return;

  List_Destroy(&(*this)->underlying_list);
  free(*this);
  *this = NULL;
} /* end of void Stack_Destroy(Stack*) */

Object Stack_Peek(const Stack this, jmp_buf sp_c) /* throws Stack_Empty */ {
  Object res;

  if (List_IsEmpty(this->underlying_list))
    longjmp(sp_c, EXC_STACK_EMPTY);

由於當控制到達此點時,我們已經確保堆疊不為空——透過檢查底層列表,如之前的 if 語句中——因此傳遞緩衝區沒有意義,緩衝區用於傳遞有關任何問題的的資訊。因此,我們將 NULL 作為第二個引數傳遞。

res = List_GetFirst(this->underlying_list, NULL);
  return res;
} /* end of Object Stack_Peek(const Stack, jmp_buf) */

Object Stack_Pop(const Stack this, jmp_buf sp_c) /* throws StackEmpty */ {
  jmp_buf rfc;
  Object res;

  if (setjmp(rfc) == EXC_LIST_EMPTY)
    longjmp(sp_c, EXC_STACK_EMPTY);
  res = List_RemoveFromFront(this->underlying_list, rfc);

  return res;
} /* end of Object Stack_Pop(const Stack, jmp_buf) */

void Stack_Push(const Stack this, Object new_item) {
  List_InsertInFront(this->underlying_list, new_item);
} /* end of void Stack_Push(const Stack, Object) */

BOOL Stack_IsEmpty(const Stack this) {
  return(List_IsEmpty(this->underlying_list));
} /* end of BOOL Stack_IsEmpty(const Stack) */

註釋

[edit | edit source]
  1. ISO C 要求 EOF 被定義為一個負的整型常量。
  2. 這當然可以透過移除錯誤處理來避免,但這會導致更大的災難。
  3. 本質上,異常處理是由作業系統提供的服務。我們還應該新增編譯器支援,它會生成相關的程式碼。
  4. 首先確保你執行了名為 vcvars32.bat 的批處理檔案,該檔案位於 Microsoft Visual C/C++ 編譯器的 bin 子目錄中,然後在 Visual Studio .NET 命令提示符中發出命令
    cl /w /FeExecName.exe CsourceName.c /link /SAFESEH:NO↵
    並執行可執行檔案。
  5. TIB 代表執行緒資訊塊,它儲存特定於當前執行緒的資訊。
  6. 所有 Win32 程序的第一個分割槽對使用者不可訪問。嘗試讀取或寫入此分割槽(其大小可能因作業系統而異)會導致訪問衝突,並導致程序終止。這對於處理 C 中的 NULL 指標操作是一個相當實用的工具:如果 NULL 宏被定義為零或第一個分割槽範圍內的任何地址值,則在沒有編譯器干擾的情況下,任何嘗試解引用 NULL 指標最終都會導致訪問衝突。
  7. 注意,我們優先選擇清晰度而不是簡潔。這個程式碼塊當然可以寫得更簡潔。
  8. 你早就知道它會依賴於機器!畢竟,作為狀態資訊的一部分,我們需要儲存一堆暫存器值,而這些值在不同的架構之間是不同的(在數量和大小上)。
  9. 這支援我們對控制代碼作為智慧指標的看法。
  10. 注意它與丟擲異常的相似之處。
  11. 非常類似於 catch 塊的主體。
  12. SIGUSR1 的預設處理程式會做什麼?
華夏公益教科書