Programming Language Concepts Using C and C++/Exception Handling

From Wikibooks, open books for an open world
Jump to navigation Jump to search

Exception and error handling in C, if there is any, is done through special return values that cannot be produced by normal completion of the function call. Take printf or one of its friends, for instance. If everything is OK a call to one of these functions normally returns the number of characters sent to the output stream. In case of an error, EOF[1] is returned—a value that would never be returned if everything went OK.

Or, say you implement a function that finds the number of occurrences of a particular item in a container. That would normally mean returning zero or a positive integer. What if the container does not exist and you want to provide this as a feedback? Returning zero doesn’t help; it means there is no item in the container. A positive value will not be of any help, either. How about returning a negative value, such as -1?

That answers our worries related to handling exceptional conditions. But writing code to the specification will not be very much fun. Consider the following code fragment, where the programmer does her best to provide controls after each function call.

... 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) */ ...

Looks messy, uh? Figuring out the control flow is an unbearable task of plowing through the countless if’s and else-if’s. Code is cluttered with error handling code, which makes it a nightmare for the maintainers.[2] It almost makes you think it couldn’t get any worse.

If only we could guarantee a failure-free program, the above code would be cut down to two lines and be much easier to read and understand. Or, if only we could isolate error handling code from the rest that would be great. That’s exactly what is done by exception handling mechanism found in many languages: isolate parts of the code that deal with unexpected conditions so that programs can be more easily maintained.

This, however, is not possible in C. In C, you must either take the well-known path [and fill your code with a zillion if’s and else-if’s] or use the setjmp-longjmp pair. In this handout, we will take a look at the latter and provide introduction to two complimentary notions, assertions and signals.

But, before we move on—in order to give some inspiration on the inner workings of exception handling mechanism—we’ll say a few words about how exceptions are handled in Win32 operating systems.[3]

Exception Handling in Win32: Structured Exception Handling (SEH)[edit]

Fundamental to exception handling in Win32 is the registration of exceptions. This is done by inserting an exception record into a linked structure, the first node of which is pointed to by FS:[0]; as try-catch blocks are entered and left, new records are inserted and removed, respectively.

For a simple presentation, compile (and link) the following C program and run the executable.[4]

Exc_Test.c
1 #include <windows.h>
2 #include <stdio.h>
3 
4 DWORD scratch = 2;

Next function contains the handler code for EXCEPTION_ACCESS_VIOLATION, which means the thread has tried to access some out-of-reach memory. This can happen as a result of attempting to write to a read-only section of memory or reading from some region without appropriate rights. As this exception is handled by moving some valid address value into EAX, we return with ExceptionContinueExecution meaning the instruction that generated the exception will be tried once more.

If the thrown exception is not an EXCEPTION_ACCESS_VIOLATION, control is transferred to the next handler in the list by returning ExceptionContinueSearch.

struct _CONTEXT, not surprisingly a CPU-dependent structure, is made up of fields containing the machine state and ContextRecord is meant to hold the snapshot taken at the time the exception takes place. Complementing this is the CPU-independent structure holding information about the most recently raised exception, such as its type, address of the instruction it occurred, and so on. When an exception occurs, the operating system pushes these structures, together with a third one containing pointers to each of them, on the stack of thread that raised the exception.

 6 EXCEPTION_DISPOSITION __cdecl av_handler(
 7   struct _EXCEPTION_RECORD *ExcRec,
 8   void* EstablisherFrame,
 9   struct _CONTEXT *ContextRecord,
10   void* DispatcherContext) {
11   printf("In the handler for ACCESS_VIOLATION\n");
12   if (ExcRec->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
13     ContextRecord->EAX = (DWORD) &scratch;
14     printf("Handled access violation exception...\n");
15   } else {
16       printf("Cannot handle exceptions other than acc. violation!\n");
17       printf("Moving on to the next handler in the list\n");
18       return ExceptionContinueSearch;
19     }
20 
21   return ExceptionContinueExecution;
22 } /* end of EXCEPTION_DISPOSITION av_handler(.....) */

Here is our second handler function, which deals with the [integer] divide-by-zero exception. Nothing new with the code!

One point worth mentioning, though: handler functions could have been merged into a single one. There is no rule saying that each and every exception must have its own handler function. In our case, the following function would have served the purpose, too.

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; ...

24 EXCEPTION_DISPOSITION __cdecl dbz_handler(
25   struct _EXCEPTION_RECORD *ExcRec,
26   void* EstablisherFrame,
27   struct _CONTEXT *ContextRecord,
28   void* DispatcherContext) {
29     printf("In the handler for INT_DIVIDE_BY_ZERO\n");
30     if (ExcRec->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO) {
31       printf("Handled divide by zero exception...\n");
32       ContextRecord->EBX = (DWORD) scratch;
33     } else {
34         printf("Cannot handle exceptions other than div. by zero\n");
35         printf("Moving on to the next handler in the list\n");
36         return ExceptionContinueSearch;
37       }
38 
39   return ExceptionContinueExecution;
40 } /* end of EXCEPTION_DISPOSITION dbz_handler(.....) */
41 
42 int main(void) {
43   DWORD handler1 = (DWORD) &av_handler;
44   DWORD handler2 = (DWORD) &dbz_handler;

The following assembly block registers handlers for probable exceptions. It does this by inserting exception registration records—one for access violation and one for divide by zero—to the front of the linked list used to hold information about exception handlers. This [exception registration] structure has two fields: a pointer to the previous structure—that is, the next node in the list—and a pointer-to-the callback function to be called in case the exception occurs.

This is pretty much the code fragment that a Win32 compiler would produce on entry to a try-block: it registers code of the related catch-blocks as handlers2 by adding them to the head of a list whose first item is pointed by the value contained in FS:[0]. Upon completion of the try-block any handler(s) registered on entry are removed from the list.

46   __asm {
47     PUSH handler1      ; push address of av_handler
48     PUSH FS:[0]
49     MOV FS:[0], ESP
50 
51     PUSH handler2      ; push address of dbz_handler
52     PUSH FS:[0]
53     MOV FS:[0], ESP
54   }

By the time control reaches this point, a partial image of memory related to exception handling will be as given below.[5] Pointer emanating from the record of av_handler points at the default handler that will be called to handle any unhandled exception.

Exception handler list

Next, we try to store 1 into the four-byte memory region starting address of which is contained in EAX. Our attempt will however result in an exception, since EAX contains zero and address zero is off-limits to processes in Win32 systems.[6]

56   __asm {
57     MOV EAX, 0
58     MOV [EAX], 1
59   }
60 
61   __asm {
62     MOV EAX, 6
63     MOV EDX, 0
64     MOV EBX, 0
65     DIV EBX
66   }

Next assembly block removes the exception records inserted on entry to main.[7] This is roughly equivalent to the code a Win32 compiler would generate upon exiting from a try-catch block.

66   __asm {
67     MOV EAX, [ESP]
68     MOV FS:[0], EAX
69     ADD ESP, 8
70 
71     MOV EAX, [ESP]
72     MOV FS:[0], EAX
73     ADD ESP, 8
74   }
75 
76   printf("Will intentionally try dividing by zero once more!\n");

Now that dbz_handler has been removed from the list, divide-by-zero exception thrown in the following block will be handled by the default handler (namely, the UnhandledExceptionFilter system function) which simply displays the well-known, most annoying message box.</source>

78   __asm {
79     MOV EAX, 6
80     MOV EDX, 0
81     MOV EBX, 0
82     DIV EBX
83   }
84 
85   return 0;
86 } /* 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!
Insert figure

Exception Handling in C Using Microsoft Extensions[edit]

Using Microsoft extensions you can write [non-portable] C programs with exception handling, albeit somewhat differently. The following is a simple example to this.

Exc_SEH.c
1 #include <windows.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 
5 int *iptr = 0;
6 int zero = 0;
7 int res;

Following is an example to exception filter functions. An exception filter decides what kind of action is to be taken when an exception occurs. In doing so, it may react differently depending on the type of the exception. For instance, our filter function offers some remedial action in the case of an access violation exception while for others exception types it defers the decision to the next handler in the list.

 9 LONG av_filter(DWORD exc_code) {
10   if (exc_code == EXCEPTION_ACCESS_VIOLATION) {
11     printf(In the handler for ACCESS_VIOLATION\n);
12     iptr = (int *) malloc(sizeof(int));
13     *iptr = 0;
14     printf(Handled access violation exception...);
15     return EXCEPTION_EXECUTE_HANDLER;
16     } else return EXCEPTION_CONTINUE_SEARCH;
17 } /* end of LONG av_filter(DWORD) */
18 
19 int main(void) {

Similar to the constructs provided at the programming language level, Microsoft SEH has the notion of a guarded region (__try) that is followed by handler code (__except or __finally). One major difference is the number of handlers: in SEH one can have either one of __except or __finally following a particular __try and only once.

Before executing the guarded region, related handler–lines between 18 and 22–is registered with the operating system by means of the compiler-synthesized code, which is roughly equivalent to lines 39-46 of the previous section. Following this the guarded region is executed and results in an exception, which is filtered through the expression passed to __except. In our case, filter decides to go on with execution of the current handler if it’s a divide-by-zero exception; otherwise, it defers the decision to the next handler in the list.

By the time control reaches line 23, this same handler will have been unregistered thanks to the code synthesized by the compiler, which is roughly the same with lines 57-64 of the previous section.

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

Instead of a plain filter, next __except has a filter function, which serves exactly the same purpose.

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

Since all user-written handlers are pulled down, exception due to execution of line 29 will be handled by the default handler offered by the operating system, which is responsible for handling all unhandled exceptions.

1   printf(Will intentionally try dividing by zero once more!\n);
2   res = 2 / zero;
3 
4 	return 0;
5 } /* end of int main(void) */
cl /FeExcSEH.exe Exc_SEH.c↵
ExcSEH↵
...
...
...

Module[edit]

Interface[edit]

List.h
1 #ifndef LIST_H
2 #define LIST_H

The following directive is needed to bring in the type definitions and function prototypes needed to make jumps across function boundaries. Apart from the prototypes for setjmp and longjmp, this header file includes the definition for the buffer type whose instances are used to communicate between the aforementioned functions.

 4 #include <setjmp.h>
 5 
 6 #include "General.h"
 7 #include "ds/ListIterator.h"
 8 
 9 struct LIST;
10 typedef struct LIST* List;
11 
12 typedef struct _Object_funcs {
13   COMPARISON_FUNC compare_component;
14   COPY_FUNC copy_component;
15   DESTRUCTION_FUNC destroy_component;
16 } List_Component_Funcs;
17 
18 typedef enum {ITERATOR_BACKWARD, ITERATOR_FORWARD} Iterator_type;
19 
20 #define EXC_LIST_EMPTY -21
21 
22 #define FROM_ARRAY 2
23 
24 extern List List_Create(int constructor_type, ...);
25 extern void List_Destroy(List*);

jmp_buf is a machine-dependent[8] buffer used to hold the program state information. What we do is basically fill in this buffer before we execute a block of code that can possibly produce an exception and, in case an exception is raised, use the values found in it to produce the environment that existed prior to the execution of the block.

27 extern Object List_GetFirst(const List this, jmp_buf get_first_caller)
28   /* throws ListEmpty */ ;
29 extern Object List_GetLast(const List this, jmp_buf get_last_caller) 
30   /* throws ListEmpty */ ;
31 
32 extern void List_InsertAtEnd(const List, Object new_item);
33 extern void List_InsertInFront(const List, Object new_item);
34 
35 extern Object List_RemoveFromEnd(const List this, jmp_buf rmv_from_end_caller)
36   /* throws ListEmpty */ ;
37 extern Object List_RemoveFromFront(const List this, jmp_buf rmv_from_front_caller)
38   /* throws ListEmpty */ ;
39 
40 extern BOOL List_IsEmpty(const List);
41 extern int List_Size(const List);
42 
43 extern List List_Merge(const List, const List otherList);
44 extern Object* List_ToArray(const List this, jmp_buf to_array_caller)
45   /* throws ListEmpty */ ; 
46 
47 extern ListIterator List_ListIterator(const List, Iterator_type it_type);
48 
49 #endif


ListExtended.h
 1 #ifndef LISTEXTENDED_H
 2 #define LISTEXTENDED_H
 3 
 4 #include <setjmp.h>
 5 
 6 #include "General.h"
 7 #include "ds/List.h"
 8 
 9 #define EXC_LIST_NOSUCHITEM -26
10 
11 extern void List_InsertAfterFirst(const List, Object, Object, jmp_buf ins_after_first_caller)
12   /* throws NoSuchItem */ ;
13 extern void List_InsertAfterLast(const List, Object, Object, jmp_buf ins_after_last_caller)
14   /* throws NoSuchItem */ ;
15 extern void List_InsertAfterNth(const List, Object, Object, int, jmp_buf ins_after_Nth_caller)
16   /* throws NoSuchItem */ ;
17 
18 extern void List_InsertBeforeFirst(const List, Object, Object, jmp_buf ins_before_first_caller)
19   /* throws NoSuchItem */ ;
20 extern void List_InsertBeforeLast(const List, Object, Object, jmp_buf ins_before_last_caller)
21   /* throws NoSuchItem */ ;
22 extern void List_InsertBeforeNth(const List, Object, Object, int, jmp_buf ins_before_Nth_caller)
23   /* throws NoSuchItem */ ;
24 
25 extern void List_InsertInAscendingOrder(const List, Object);
26 extern void List_InsertInAscendingOrderEx(const List, Object, COMPARISON_FUNC);
27 extern void List_InsertInDescendingOrder(const List, Object);
28 extern void List_InsertInDescendingOrderEx(const List, Object, COMPARISON_FUNC);
29 
30 extern int List_RemoveAll(const List, Object, jmp_buf rmv_all_caller)
31   /* throws ListEmpty */ ;
32 extern void List_RemoveFirst(const List, Object, jmp_buf rmv_first_caller)
33   /* throws ListEmpty, NoSuchItem */ ;
34 extern void List_RemoveLast(const List, Object, jmp_buf rmv_last_caller)
35   /* throws ListEmpty, NoSuchItem */ ;
36 extern void List_RemoveNthFromEnd(const List, Object, int, jmp_buf rmv_Nth_from_end_caller)
37   /* throws ListEmpty, NoSuchItem */ ;
38 extern void List_RemoveNthFromFront(const List, Object, int, jmp_buf rmv_Nth_from_front_caller)
39   /* throws ListEmpty, NoSuchItem */ ;
40 
41 #endif


ListIterator.h
 1 #ifndef LISTITERATOR_H
 2 #define LISTITERATOR_H
 3 
 4 #include <setjmp.h>
 5 
 6 #include "General.h"
 7 
 8 #define EXC_ITERATOR_ILLEGALSTATE -31
 9 #define EXC_ITERATOR_ILLEGALARGUMENT -32
10 #define EXC_ITERATOR_NOSUCHELEMENT -33
11 #define EXC_ITERATOR_UNSUPPORTEDOPERATION -34
12 
13 struct LISTITERATOR;
14 typedef struct LISTITERATOR* ListIterator;
15 
16 extern void ListIterator_Destroy(ListIterator*);
17 
18 extern BOOL ListIterator_HasNext(const ListIterator);
19 extern Object ListIterator_Next(const ListIterator, jmp_buf next_caller)
20   /* throws NoSuchElement */ ;
21 extern int ListIterator_NextIndex(const ListIterator);
22 
23 extern BOOL ListIterator_HasPrevious(const ListIterator);
24 extern Object ListIterator_Previous(const ListIterator, jmp_buf prev_caller)
25   /* throws NoSuchElement */ ;
26 extern int ListIterator_PreviousIndex(const ListIterator);
27 
28 extern void ListIterator_Remove(const ListIterator, jmp_buf  rmv_caller)
29   /* throws IllegalState, UnsupportedOperation */ ;
30 extern void ListIterator_Add(const ListIterator, Object, jmp_buf add_caller)
31   /* throws IllegalState, UnsupportedOperation */ ;
32 extern void ListIterator_Set(const ListIterator, Object, jmp_buf set_caller)
33   /* throws IllegalArgument, IllegalState, UnsupportedOperation */ ;
34 
35 #endif

Implementation[edit]

List.c
 1 #include <setjmp.h>
 2 #include <stdarg.h>
 3 #include <stddef.h>
 4 #include <stdio.h>
 5 #include <stdlib.h>
 6 
 7 #include "General.h"
 8 #include "ds/ListExtended.h"
 9 #include "ds/ListIterator.h"
10 
11 #define IT_OPERATION_NOOP 0
12 #define IT_OPERATION_ADD 1
13 #define IT_OPERATION_NEXT 2
14 #define IT_OPERATION_PREVIOUS 3
15 #define IT_OPERATION_REMOVE 4
16 #define IT_OPERATION_SET 5

Déjà vu all over again: A handle pointing to some metadata, which in turn contains a pointer to the container that holds the components of the collection.

Underlying structure of a list

This recurring pattern is a good starting point when you design a collection. Handle is there to prevent users from manipulating the data structure directly and is also useful for maintenance since it takes up constant amount of memory; metadata is there to hold the state information about and/ or attributes of the structure; and container is there to physically hold the data.

Observe we make use of a type (struct _LIST) that is yet to be defined. Compiler does not complain a bit about it because we don’t use the type itself but a pointer to the type, which has a known size.

18 struct LIST {
19   COMPARISON_FUNC compare_component;
20   COPY_FUNC copy_component;
21   DESTRUCTION_FUNC destroy_component;
22   unsigned int size;
23   struct _LIST* head;
24 };

What follows is the definition of a single list node. This definition, which is tightly coupled with the list, could have been moved into the list type definition as shown below.

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 version of the following definition involves using the type name being defined while in C this is impossible. In C, a type name cannot be used before its definition is completed. This is not a problem for pointers, though. Whatever the size of the object they manipulate, their size does not change. For this reason, whenever we use recursion in the description of a data type it is likely to be formulated with a pointer.[9]

26 struct _LIST {
27   Object info;
28   struct _LIST* next;
29   struct _LIST* prev;
30 };
31 typedef struct _LIST* _List;

All operations applied on a ListIterator object will effectively act on an underlying List object. Now that we can have more than one List object in an application and a particular iterator can be used to traverse only one of them, in addition to the iterator specific fields, our structure contains a reference to the enclosing List structure.

Structure of the iterator

This is similar to what gets done in the implementation of inner classes in Java: when transformed into Java 1.0 for the purpose of generating Java virtual machine bytecodes, the signature of each non-static inner class constructor is modified so that it receives the enclosing instance as a first argument. As soon as the constructor takes control, this value is stored in a private field. For example,

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

will be transformed into

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

Remark the access specifier for the List$Iterator class: package-friendly, not private. This is due to the fact that top-level classes cannot be private or protected. But then, why not use public instead? The answer is because there can be only one public class in a single file and that happens to be the List class.

But, promoting Iterator from private to package-friendly means it can be used by classes for which it was not intended to. The answer lies in usage of the synthesized class name: such a name cannot be used in the source code and this is enforced by the compiler.

 33 struct LISTITERATOR {
 34   List underlying_list;
 35   _List ptr;
 36   int index;
 37   int last_op;
 38 };
 39 
 40 static _List create_sublist(Object info);
 41 
 42 List List_Create(int type, ...) {
 43   int i, array_len;
 44   jmp_buf li_n;
 45   va_list ap;
 46   List_Component_Funcs funcs;
 47   ListIterator itr;
 48   List existing_list;
 49   Object* from_array;
 50 
 51   List ret_list = (List) malloc(sizeof(struct LIST));	
 52   if (!ret_list) {
 53     fprintf(stderr, "Out of memory...\n");
 54     return(NULL);
 55   } /* end of if(!ret_list) */
 56 
 57   ret_list->size = 0;
 58   ret_list->head = NULL;
 59   va_start(ap, type);
 60   switch(type) {
 61     case COPY:
 62       existing_list = va_arg(ap, List);
 63       ret_list->compare_component = existing_list->compare_component;
 64       ret_list->copy_component = existing_list->copy_component;
 65       ret_list->destroy_component = existing_list->destroy_component;
 66       itr = List_ListIterator(existing_list, ITERATOR_FORWARD);
 67       for(; ListIterator_HasNext(itr); )
 68         List_InsertAtEnd(ret_list, existing_list->copy_component(ListIterator_Next(itr, li_n)));
 69       free(itr);
 70       break;
 71     case DEFAULT:
 72       funcs = va_arg(ap, List_Component_Funcs);
 73       ret_list->compare_component = funcs.compare_component;
 74       ret_list->copy_component = funcs.copy_component;
 75       ret_list->destroy_component = funcs.destroy_component;
 76       break;
 77     case FROM_ARRAY:
 78       funcs = va_arg(ap, List_Component_Funcs);
 79       from_array = va_arg(ap, Object*);
 80       array_len = va_arg(ap, int);
 81       ret_list->compare_component = funcs.compare_component;
 82       ret_list->copy_component = funcs.copy_component;
 83       ret_list->destroy_component = funcs.destroy_component;
 84       for (i = 0; i < array_len; i++)
 85         List_InsertAtEnd(ret_list, ret_list->copy_component(from_array[i]));
 86       break;
 87   } /* end of switch(type) */
 88   va_end(ap);
 89 
 90   return ret_list;
 91 } /* end of List List_Create(List_Constructor_type, ...) */
 92 
 93 void List_Destroy(List* this) {
 94   int size, i;
 95 
 96   if (*this == NULL) return;
 97 
 98   size = (*this)->size; i = 1;
 99   for (; i <= size; i++) 
100     (*this)->destroy_component(List_RemoveFromFront(*this));
101   free(*this);
102   *this = NULL;
103 } /* end of void List_Destroy(List*) */
104 
105 Object List_GetFirst(const List this, jmp_buf gfc) /* throws List_Empty */ {

longjmp is a function that provides for jumps across function boundaries. In doing so, it makes use of the jmp_buf structure formerly filled in by a call to the setjmp function.

In a way longjmp can be seen as 'a jump on steroids'. In addition to jumping to some other instruction [probably in a different function], it ensures that the program is in a valid state by restoring the machine state with the values found in the jmp_buf structure. So, the following longjmp will jump to the location where the corresponding setjmp was issued and unwind the runtime stack in the meantime. While doing so it returns information about the nature of the exceptional condition in the second argument passed to it, which in this case is supposed to reflect the fact that the list we want to peek in is an empty one.

Note that this unwinding process does not undo any modifications made on memory or the file system; it simply re-establishes the machine state as it was before, any changes made on the contents of primary and secondary memory is retained. In case you may need a more complete unwind you must take charge and do it yourself.

This type of behavior is similar to that of an exception: when a thrown exception is not handled in the current subprogram, runtime stack is unwound and control is transferred back to the callee—that is actually a jump across the current function’s boundary—hoping it will take care of the exception.

106   if (List_IsEmpty(this)) longjmp(gfc, EXC_LIST_EMPTY);
107 
108   return this->head->info;
109 } /* end of Object List_GetFirst(const List) */
110 
111 Object List_GetLast(const List this, jmp_buf glc) /* throws List_Empty */ {
112   if (List_IsEmpty(this)) longjmp(glc, EXC_LIST_EMPTY);
113 
114   return this->head->prev->info;
115 } /* end of Object List_GetLast(const List, jmp_buf) */
116 
117 static _List create_sublist(Object info) {
118   _List this = (_List) malloc(sizeof(struct _LIST));
119   if (!this) return NULL;
120 
121   this->info = info;
122   this->prev = this->next = this;
123 
124   return this;
125 } /* end of _List create_sublist(Object) */
126 
127 void List_InsertAtEnd(const List this, Object new_item) {
128   _List new_sublist = create_sublist(new_item);
129 
130   this->size++;
131   if (List_IsEmpty(this)) this->head = new_sublist;
132     else {
133       _List tail = this->head->prev;      
134       new_sublist->prev = tail;
135       tail->next = new_sublist;
136       new_sublist->next = this->head;
137       this->head->prev = new_sublist;
138     } /* end of else */
139 } /* end of void List_InsertAtEnd(const List, Object) */
140 
141 void List_InsertInFront(const List this, Object new_item) {
142   _List new_sublist = create_sublist(new_item);
143 
144   this->size++; 
145   if (List_IsEmpty(this)) this->head = new_sublist;
146     else {
147       _List head = this->head;
148       _List tail = this->head->prev;
149       this->head = new_sublist;
150       new_sublist->next = head;
151       new_sublist->prev = tail;
152       tail->next = new_sublist;
153       head->prev = new_sublist;
154     } /* end of else */
155 } /* end of void List_InsertInFront(const List, Object) */
156 
157 Object List_RemoveFromEnd(const List this, jmp_buf rec) /* throws ListEmpty */ {
158   _List tail;
159   Object retVal;
160 
161   if (List_IsEmpty(this)) longjmp(rec, EXC_LIST_EMPTY);
162 
163   this->size--;
164   tail = this->head->prev;
165   retVal = tail->info;
166   if (this->size != 0) {
167     _List new_tail = tail->prev;
168     new_tail->next = this->head;
169     this->head->prev = new_tail;
170   } else this->head = NULL;
171 
172   tail->next = tail->prev = NULL;
173   tail->info = NULL;
174   free(tail); 
175 
176   return retVal;
177 } /* end of Object List_RemoveFromEnd(const List, jmp_buf) */
178 
179 Object List_RemoveFromFront(const List this, jmp_buf rfc) /* throws ListEmpty */ {
180   _List head;
181   Object retVal;
182 
183   if (List_IsEmpty(this)) longjmp(rfc, EXC_LIST_EMPTY);
184 
185   this->size--;
186   head = this->head;
187   retVal = head->info;
188   if (this->size != 0) {
189     head->prev->next = head->next;
190     head->next->prev = head->prev;
191     this->head = head->next;
192   } else this->head = NULL;
193 
194   head->next = head->prev = NULL;
195   head->info = NULL;
196   free(head); 
197 
198   return retVal;
199 } /* end of Object List_RemoveFromFront(const List, jmp_buf) */
200 
201 BOOL List_IsEmpty(const List this) {
202   if (this->head == NULL) return TRUE;
203     else return FALSE;
204 } /* end of BOOL List_IsEmpty(const List) */
205 
206 int List_Size(const List this) { return this->size; }
207 
208 Object* List_ToArray(const List this, jmp_buf tac) /* throws ListEmpty */ {
209   int size = this->size, i;
210   jmp_buf li_n;
211   ListIterator itr;
212   Object* ret_array;
213 
214   if (size == 0) longjmp(tac, EXC_LIST_EMPTY);
215 
216   ret_array = malloc(sizeof(Object) * size);
217   if (!ret_array) {
218     fprintf(stderr, Out of memory...\n);
219     return(NULL);
220   } /* end of if(!ret_array) */
221 
222   i = 0; itr = List_ListIterator(this, ITERATOR_FORWARD);
223   for (; ListIterator_HasNext(itr); i++)
224     ret_array[i] = ListIterator_Next(itr, li_n);
225   free(itr);
226 
227   return ret_array;
228 } /* end of Object[] List_ToArray(const List, jmp_buf) */
229 
230 List List_Merge(const List this, const List second_list) {
231   jmp_buf li_n;
232   List_Component_Funcs funcs = { this->compare_component, this->copy_component, this->destroy_component };
233   List ret_list = List_Create(DEFAULT, funcs);
234   List l1 = this, l2 = second_list;
235   ListIterator itr;
236 
237   itr = List_ListIterator(l1, ITERATOR_FORWARD);
238   for(; ListIterator_HasNext(itr);)
239     List_InsertAtEnd(ret_list, 
240   ret_list->copy_component(ListIterator_Next(itr, li_n)));
241   free(itr);
242 
243   itr = List_ListIterator(l2, ITERATOR_FORWARD);
244   for(; ListIterator_HasNext(itr);)
245     List_InsertAtEnd(ret_list,
246   ret_list->copy_component(ListIterator_Next(itr, li_n)));
247   free(itr);
248 
249   return ret_list;
250 } /* end of List List_Merge(const List, const List) */
251 
252 ListIterator List_ListIterator(const List this, Iterator_type it_type) {
253   ListIterator ret_iterator = (ListIterator) malloc(sizeof(struct LISTITERATOR));
254   if (!ret_iterator) {
255     fprintf(stderr, Out of memory...\n);
256     return(NULL);
257   } /* end of if(!ret_iterator) */
258 
259   ret_iterator->underlying_list = this;
260   ret_iterator->ptr = this->head; 
261   ret_iterator->last_op = IT_OPERATION_NOOP;
262   if (it_type == ITERATOR_FORWARD) ret_iterator->index = 0;
263     else ret_iterator->index = this->size;
264 
265   return ret_iterator;
266 } /* end of ListIterator List_ListIterator(const List, Iterator_type) */
267 
268 void List_InsertAfterFirst(const List this, Object new_item, Object after, jmp_buf iafc)
269   /* throws NoSuchItem */ {
270   jmp_buf ins_a_Nth;

Executing the setjmp command will fill in the jmp_buf argument passed to it with values that reflect the machine state at the point of its invocation. As it completes, setjmp will return 0. Next step is to call the function (List_InsertAfterNth) that may give rise to an exceptional situation. If everything goes our way, control will return to the statement following the function invocation, which is a return in our case. Otherwise, control will return to the point where setjmp was issued and return the value returned by the invocation of longjmp in List_InsertAfterNth as the result of calling the setjmp.

Passing context information

We can summarize what happens as follows:

  • Record the machine state by a call to setjmp and return 0.
  • Call the function that may give rise to an exceptional condition.
  • If everything is OK, return a legitimate value from the function by a return statement. Control will flow as if there were no special arrangements made. Otherwise, return from the function by a longjmp function. Doing so will (in addition to unwinding the runtime stack) return control to the point where setjmp function was called. Pretend as if setjmp were not called before and return as its result the value that was returned in the second argument of the longjmp function.
272   if (setjmp(ins_a_Nth) == EXC_LIST_NOSUCHITEM)
273     longjmp(iafc, EXC_LIST_NOSUCHITEM);
274   List_InsertAfterNth(this, new_item, after, 1, ins_a_Nth);
275 } /* end of void List_InsertAfterFirst(const List, Object, Object, jmp_buf) */
276 
277 void List_InsertAfterLast(const List this, Object new_item, Object aft, jmp_buf ialc)
278   /* throws NoSuchItem */ {
279   jmp_buf li_a, li_n, li_p;
280   ListIterator itr = List_ListIterator(this, ITERATOR_BACKWARD);
281 
282   for(; ListIterator_HasPrevious(itr); ) 
283     if (this->compare_component(aft, ListIterator_Previous(itr, li_p)) == 0) {
284       ListIterator_Next(itr, li_n);
285       ListIterator_Add(itr, new_item, li_a);
286       free(itr);
287       return;
288     } /* end of if(this->compare_component(aft, …) == 0) */
289 
290   free(itr);
291   longjmp(ialc, EXC_LIST_NOSUCHITEM);
292 } /* end of void List_InsertAfterLast(const List, Object, Object, jmp_buf) */
293 
294 void List_InsertAfterNth(const List this, Object new_item, Object aft, int n, jmp_buf iaNc)
295   /* throws NoSuchItem */ {
296   int index = 0;
297   jmp_buf li_a, li_n;
298   ListIterator itr = List_ListIterator(this, ITERATOR_FORWARD);
299 
300   for(; index < n && ListIterator_HasNext(itr); )
301     if (this->compare_component(aft, ListIterator_Next(itr, li_n)) == 0)
302       index++;
303   if (index < n) { 
304     free(itr);
305     longjmp(iaNc, EXC_LIST_NOSUCHITEM);
306   } /* end of if (index < n) */
307   ListIterator_Add(itr, new_item, li_a);
308   free(itr);
309 } /* end of void List_InsertAfterNth(const List, Object, Object, int, jmp_buf) */
310 
311 void List_InsertBeforeFirst(const List this, Object new_item, Object before, jmp_buf ibfc)
312   /* throws NoSuchItem */ {
313   jmp_buf ins_b_Nth;
314 
315   if (setjmp(ins_b_Nth) == EXC_LIST_NOSUCHITEM)
316   longjmp(ibfc, EXC_LIST_NOSUCHITEM);
317   List_InsertBeforeNth(this, new_item, before, 1, ins_b_Nth);
318 } /* end of void List_InsertBeforeFirst(const List, Object, Object, jmp_buf) */
319 
320 void List_InsertBeforeLast(const List this, Object new_item, Object bef, jmp_buf iblc)
321   /* throws NoSuchItem */ {
322   jmp_buf li_a, li_p;
323   ListIterator itr = List_ListIterator(this, ITERATOR_BACKWARD);
324 
325   for(; ListIterator_HasPrevious(itr); ) 
326     if (this->compare_component(bef, ListIterator_Previous(itr, li_p)) == 0) {
327       ListIterator_Add(iterator, new_item, li_a);
328       free(itr);
329       return;
330     } /* end of if (this->compare_component…) */
331 
332   free(itr);
333   longjmp(iblc, EXC_LIST_NOSUCHITEM);
334 } /* end of void List_InsertBeforeLast(const List, Object, Object, jmp_buf) */
335 
336 void List_InsertBeforeNth(const List this, Object new_item, Object before, int n, jmp_buf ibNc)
337   /* throws NoSuchItem */ {
338   int index = 0;
339   jmp_buf li_a, li_n, li_p;
340   ListIterator itr = List_ListIterator(this, ITERATOR_FORWARD);
341 
342   for(; index < n && ListIterator_HasNext(itr); )
343     if (this->compare_component(before, ListIterator_Next(itr, li_n)) == 0) 
344       index++;
345 
346   if (index < n) { 
347     free(itr); 
348     longjmp(ibNc, EXC_LIST_NOSUCHITEM);
349   } /* end of if (index < n) */
350 
351   ListIterator_Previous(itr, li_p);
352   ListIterator_Add(itr, new_item, li_a);
353   free(itr);
354 } /* end of void List_InsertBeforeNth(const List, Object, Object, int, jmp_buf) */
355 
356 void List_InsertInAscendingOrder(const List this, Object new_item) {
357   jmp_buf li_a, li_n, li_p;
358   ListIterator itr = List_ListIterator(this, ITERATOR_BACKWARD);
359 
360   for (; ListIterator_HasPrevious(itr); ) {
361     Object next_item = ListIterator_Previous(itr, li_p);
362     if (this->compare_component(new_item, next_item) < 0) continue;
363     ListIterator_Next(itr, li_n);
364     ListIterator_Add(itr, new_item, li_a);
365     free(itr);
366     return;
367   } /* end of for (; ListIterator_HasPrevious(itr); ) */
368   ListIterator_Add(itr, new_item, li_a);
369   free(itr);
370 } /* end of void List_InsertInAscendingOrder(const List, Object) */
371 
372 void List_InsertInAscendingOrderEx(const List this, Object new_item, COMPARISON_FUNC cmp) {
373   jmp_buf li_a, li_n, li_p;
374   ListIterator itr = List_ListIterator(this, ITERATOR_BACKWARD);
375 
376   for (; ListIterator_HasPrevious(itr); ) {
377     Object next_item = ListIterator_Previous(itr, li_p);
378     if (cmp(new_item, next_item) < 0) continue;
379     ListIterator_Next(itr, li_n);
380     ListIterator_Add(itr, new_item, li_a);
381     free(itr);
382     return;
383   } /* end of for (; ListIterator_HasPrevious(itr); )*/
384   ListIterator_Add(itr, new_item, li_a);
385   free(itr);
386 } /* end of void List_InsertInAscendingOrderEx(const List, Object, COMPARISON_FUNC) */
387 
388 void List_InsertInDescendingOrder(const List this, Object new_item) {
389   jmp_buf li_a, li_n, li_p;
390   ListIterator itr = List_ListIterator(this, ITERATOR_BACKWARD);
391 
392   for (; ListIterator_HasPrevious(itr); ) {
393     Object next_item = ListIterator_Previous(itr, li_p);
394     if (this->compare_component(new_item, next_item) > 0) continue;
395     ListIterator_Next(itr, li_n);
396     ListIterator_Add(itr, new_item, li_a);
397     free(itr);
398     return;
399   } /* end of for (; ListIterator_HasPrevious(itr); )*/
400   ListIterator_Add(itr, new_item, li_a);
401   free(itr);
402 } /* end of void List_InsertInDescendingOrder(const List, Object) */
403 
404 void List_InsertInDescendingOrderEx(const List this, Object new_item, COMPARISON_FUNC cmp) {
405   jmp_buf li_a, li_n, li_p;
406   ListIterator itr = List_ListIterator(this, ITERATOR_BACKWARD);
407 
408   for (; ListIterator_HasPrevious(itr); ) {
409     Object next_item = ListIterator_Previous(itr, li_p);
410     if (cmp(new_item, next_item) > 0) continue;
411     ListIterator_Next(itr, li_n);
412     ListIterator_Add(itr, new_item, li_a);
413     free(itr);
414     return;
415   } /* end of for (; ListIterator_HasPrevious(itr); )*/
416   ListIterator_Add(itr, new_item, li_a);
417   free(itr);
418 } /* end of void List_InsertInDescendingOrderEx(const List, Object, COMPARISON_FUNC) */
419 
420 int List_RemoveAll(const List this, Object item, jmp_buf rac) /* throws ListEmpty */ {
421   int i = 0;
422   jmp_buf li_n, li_r;
423   ListIterator itr;
424 
425   if (List_IsEmpty(this)) longjmp(rac, EXC_LIST_EMPTY);
426 
427   for (itr = List_ListIterator(this, ITERATOR_FORWARD);
428     ListIterator_HasNext(itr); )
429   if (this->compare_component(item, ListIterator_Next(itr, li_n)) == 0) {
430     ListIterator_Remove(itr, li_r);
431     i++;
432   } /* end of if(->compare_component(item, …) == 0) */
433   free(itr);
434 
435   return i;
436 } /* end of int List_RemoveAll(const List, Object, jmp_buf) */
437 
438 void List_RemoveFirst(const List this, Object new_item, jmp_buf rfc) /* throws ListEmpty, NoSuchItem */ {
439   jmp_buf rmv_Nth;
440 
441   switch (setjmp(rmv_Nth)) {
442     case 0: break;
443     case EXC_LIST_EMPTY: longjmp(rfc, EXC_LIST_EMPTY);
444     case EXC_LIST_NOSUCHITEM: longjmp(rfc, EXC_LIST_NOSUCHITEM);
445   } /* end of switch(setjmp(rmv_Nth)) */
446   List_RemoveNthFromFront(this, new_item, 1, rmv_Nth);
447 } /* end of void List_RemoveFirst(const List, Object, jmp_buf) */
448 
449 void List_RemoveLast(const List this, Object new_item, jmp_buf rlc) /* throws ListEmpty, NoSuchItem */ {
450   jmp_buf rmv_Nth;
451   int status = setjmp(rmv_Nth);
452 
453   switch (status) {
454     case 0: break;
455     case EXC_LIST_EMPTY: longjmp(rlc, EXC_LIST_EMPTY);
456     case EXC_LIST_NOSUCHITEM: longjmp(rlc, EXC_LIST_NOSUCHITEM);
457   } /*  end of switch(status) */
458   List_RemoveNthFromEnd(this, new_item, 1, rmv_Nth);
459 } /* end of void List_RemoveLast(const List, Object, jmp_buf) */
460 
461 void List_RemoveNthFromEnd(const List this, Object new_itm, int n, jmp_buf rNec)
462   /* throws ListEmpty, NoSuchItem */ {
463   int i = 0;
464   jmp_buf li_p, li_r;
465   ListIterator itr;
466 
467   if (List_IsEmpty(this)) longjmp(rNec, EXC_LIST_EMPTY);
468 
469   for (itr = List_ListIterator(this, ITERATOR_BACKWARD); ListIterator_HasPrevious(itr) && i < n; )
470     if (this->compare_component(new_itm, ListIterator_Previous(itr, li_p)) == 0) 
471   i++;
472 
473   if (i == n) {
474     ListIterator_Remove(itr, li_r);
475     free(itr);
476   } else { 
477       free(itr);
478       longjmp(rNec, EXC_LIST_NOSUCHITEM);
479     } /* end of else */
480 } /* end of void List_RemoveNthFromEnd(const List, Object, int, jmp_buf) */
481 
482 void List_RemoveNthFromFront(const List this, Object new_itm, int n, jmp_buf rNfc) 
483   /* throws ListEmpty, NoSuchItem */ {
484   int i = 0;
485   jmp_buf li_n, li_r;
486   ListIterator itr;
487 
488   if (List_IsEmpty(this)) longjmp(rNfc, EXC_LIST_EMPTY);
489 
490   for (itr = List_ListIterator(this, ITERATOR_FORWARD); ListIterator_HasNext(itr) && i < n; )
491     if (this->compare_component(new_itm, ListIterator_Next(itr, li_n)) == 0)
492   i++;
493 
494   if (i == n) {
495     ListIterator_Remove(itr, li_r);
496     free(itr);
497   } else { 
498       free(itr);
499       longjmp(rNfc, EXC_LIST_NOSUCHITEM);
500     } /* end of else */
501 } /* end of void List_RemoveNthFromFront(const List, Object, int, jmp_buf) */
502 
503 void ListIterator_Destroy(ListIterator* this) {
504   if (*this == NULL) return;
505 
506   (*this)->underlying_list = NULL;
507   free(*this);
508   *this = NULL;
509 } /* end of void ListIterator_Destroy(ListIterator) */
510 
511 BOOL ListIterator_HasNext(const ListIterator this) {
512   if (this->index == this->underlying_list->size) return FALSE;
513     else return TRUE;
514 } /* end of BOOL ListIterator_HasNext(const ListIterator) */
515 
516 Object ListIterator_Next(const ListIterator this, jmp_buf li_nc) /* throws NoSuchElement */ {
517   Object retVal;
518 
519   if (!ListIterator_HasNext(this))
520     longjmp(li_nc, EXC_ITERATOR_NOSUCHELEMENT);
521 
522   retVal = this->ptr->info;
523   this->ptr = this->ptr->next;
524   this->index++;
525   this->last_op = IT_OPERATION_NEXT;
526 
527   return retVal;
528 } /* end of Object ListIterator_Next(const ListIterator, jmp_buf) */ 
529 
530 int ListIterator_NextIndex(const ListIterator this) {
531   return(this->index);
532 } /* end of int ListIterator_NextIndex(const ListIterator) */
533 
534 BOOL ListIterator_HasPrevious(const ListIterator this) {
535   if (this->index == 0) return FALSE;
536     else return TRUE;
537 } /* end of BOOL ListIterator_HasPrevious(const ListIterator) */
538 
539 Object ListIterator_Previous(const ListIterator this, jmp_buf li_pc) /* throws NoSuchElement */ {
540   if (!ListIterator_HasPrevious(this)) 
541     longjmp(li_pc,EXC_ITERATOR_NOSUCHELEMENT);
542 
543   this->ptr = this->ptr->prev;
544   this->index--;
545   this->last_op = IT_OPERATION_PREVIOUS;
546 
547   return this->ptr->info;
548 } /* end of BOOL ListIterator_Previous(const ListIterator, jmp_buf) */
549 
550 int ListIterator_PreviousIndex(const ListIterator this) {
551   return (this->index - 1);
552 } /* end of int ListIterator_PreviousIndex(const ListIterator) */
553 
554 void ListIterator_Remove(const ListIterator this, jmp_buf li_rc) 
555   /* throws IllegalStateException, UnsupportedOperationException */ {
556   int index; /* index of the item to be deleted */
557   _List to_be_deleted;
558 
559   if (this->last_op != IT_OPERATION_NEXT && this->last_op != IT_OPERATION_PREVIOUS)
560     longjmp(li_rc, EXC_ITERATOR_ILLEGALSTATE);
561 
562   switch (this->last_op) {
563     case IT_OPERATION_NEXT:
564       to_be_deleted = this->ptr->prev;
565       index = this->index - 1;
566       break;
567     case IT_OPERATION_PREVIOUS:
568       to_be_deleted = this->ptr;
569       this->ptr = this->ptr->next;
570       index = this->index++;
571       break;
572   } /* end of switch(this->lastOp) */
573 
574   this->last_op = IT_OPERATION_REMOVE;
575   if (index + 1 == this->underlying_list->size) /* if it is the last item in the list */ {
576     jmp_buf rmv_e_c;
577     if (setjmp(rmv_e_c) != 0) NO_OP;
578     List_RemoveFromEnd(this->underlying_list, rmv_e_c);
579   }
580   else if (index == 0) /* if it is the first item in the list */ {
581       jmp_buf rmv_f_c;
582       if (setjmp(rmv_f_c) != 0) NO_OP;
583       List_RemoveFromFront(this->underlying_list, rmv_f_c);
584   } else {
585       _List next_list = to_be_deleted->next;
586       _List prev_list = to_be_deleted->prev;
587       prev_list->next = next_list; next_list->prev = prev_list;
588       this->underlying_list->destroy_component(to_be_deleted->info);
589       this->underlying_list->size--;
590       to_be_deleted->prev = to_be_deleted->next = NULL;
591       to_be_deleted->info = NULL;
592       free(to_be_deleted);
593     }
594 
595   this->index--;
596 } /* end of void ListIterator_Remove(const ListIterator, jmp_buf) */
597 
598 void ListIterator_Add(const ListIterator this, Object new_item, jmp_buf li_ac) 
599   /* throws IllegalState, UnsupportedOperation */ {
600   _List new_sublist;
601 
602   if (!ListIterator_HasNext(this)) 
603     List_InsertAtEnd(this->underlying_list, new_item);
604   else if (!ListIterator_HasPrevious(this))
605     List_InsertInFront(this->underlying_list, new_item);
606     else {
607       new_sublist = create_sublist(new_item);
608       new_sublist->next = this->ptr;
609       new_sublist->prev = this->ptr->prev;
610       this->ptr->prev->next = new_sublist;
611       this->ptr->prev = new_sublist;
612       this->underlying_list->size++;
613     }
614 
615   this->index++;
616   this->last_op = IT_OPERATION_ADD;
617 } /* end of void ListIterator_Add(const ListIterator, Object) */
618 
619 void ListIterator_Set(const ListIterator this, Object new_value, jmp_buf li_sc)
620   /* throws IllegalArgument, IllegalState, UnsupportedOperation */ {
621   if (this->last_op == IT_OPERATION_NEXT) { 
622     this->underlying_list->destroy_component(this->ptr->prev->info); 
623     this->ptr->prev->info = new_value;
624   }
625   else if (this->last_op == IT_OPERATION_PREVIOUS) {
626     this->underlying_list->destroy_component(this->ptr->info);
627     this->ptr->info = new_value;
628   } else longjmp(li_sc, EXC_ITERATOR_ILLEGALSTATE);
629 
630   this->last_op = IT_OPERATION_SET;
631 } /* end of void ListIterator_Set(const ListIterator, Object, jmp_buf) */

Robustness and Correctness of a Program[edit]

In our test program we provide simple examples to the use of two notions complementing the exception concept: assertion facility and signals. The former is used to get the compiler to insert run-time controls into code and therefore can be seen as a tool to build correct software. The latter is a notification to a process that an event has occurred. Put differently a signal is a software interrupt—due to some unexpected condition in the computer, operating system, or a process in the system—delivered to a process.

Assertions[edit]

Assertion facility and exception handling serve similar purposes; they do not serve the same purpose. Although both facilities increase reliability they do so by addressing different aspects: robustness and correctness. While exception handling enables a more robust system by providing an ability to recover from an unexpected condition, assertions help ensure correctness by checking the validity of claims made by the developer.

Definition: Robustness pertains to a system’s ability to reasonably react to a variety of circumstances and possibly unexpected conditions. Correctness pertains to a system’s adherence to a specification that states requirements about what it (system) is expected to do.

For example, a data file missing on disk has nothing to do with the correctness of the implementation. Programmer is better off providing some code dealing with this exceptional condition. On the other hand, an illegal argument value passed to a subprogram is very likely the result of a previous mistake made, such as sloppy range-checking, in the program code.

Signals[edit]

Signals serve to complement exceptions in a different sense: while exceptions are due to the unexpected consequences of activities of the current program [and therefore is a programming language concept and can be said to be internal] signals—an operating system concept—are generally external and can be due to hardware, operating system, or processes.

Relation between exceptions and signals can be better understood by an example. Consider integer division. If the divisor happens to be zero the processor will generate a division-by-zero interrupt, which is further relayed by the operating system to the language runtime as a SIGFPE signal. This signal is finally cast by the language runtime into an exception, such as ArithmeticException, and passed back to the program containing the culprit code, hoping it will be caught and handled in this program.

Signals can be examined in three categories: program errors, external events, and explicit requests. An error means the program has performed something invalid and this has been detected by the operating system or the computer. This includes division by zero, dereferencing null pointer, dereferencing uninitialized pointer, and so on. An external event has to do with I/O or communication with other processes. Examples to this category are expiration of a timer, termination of a child process, stopping or suspending a process, yielding the user terminal to a background job for input or output, and so on. An explicit request is a library function call that specifically generates a signal, such as the abort and the kill functions, which basically generate SIGABRT and SIGKILL, respectively.

Examples of signals include:

SIGINT (Interrupt)
Upon receiving the special interrupt key, generally Ctrl-C, a SIGINT signal is sent to the process.
SIGKILL
This interrupt immediately terminates the process. There is no way to block, handle, or ignore this interrupt. A typical use of this signal and the previous one is to terminate a program that has entered an infinite loop or a program that is taking too much of the system resources.
SIGTERM
Similar to SIGKILL, this interrupt causes program termination. Unlike SIGKILL, however, it is possible to block, handle, or ignore this interrupt.
SIGSEGV (Segmentation violation)
Program is trying to read/ write the memory that is not allocated for it. This signal may be generated when an array is used with an out-of-bounds index value or an uninitialized pointer with a properly aligned initial value is dereferenced.
SIGILL (Illegal Instruction)
An illegal or privileged instruction has been encountered. Such a signal may be raised as a result of executing a binary that is meant for a different processor, which can be exemplified by running an executable containing Pentium-4 instructions on an i386. One other cause is attempting to execute a corrupted executable. Finally, trying to execute a privileged instruction- such as those manipulating the system tables- in a user program will also give rise to generation of this signal.
SIGBUS
An attempt to access invalid address has been made. This is probably due to trying to access misaligned data, which may happen when an uninitialized pointer with a random value in it is dereferenced.
SIGFPE
A general arithmetic exception has happened. Note that this is not restricted to floating-point numbers, as its name may suggest. It can be integer division-by-zero, overflow, and so on.
SIGALRM
This signal is generated whenever a previously set timer expires.
SIGUSR1 and SIGUSR2
Set aside for the programmer, these signals can be tailored to meet any requirement.

When a signal is raised (or generated) it becomes pending, which means it is awaiting to be delivered to the process. If not blocked by the process this usually takes a very short time and upon delivery a certain routine is performed for dealing with the signal. This is called “handling the signal” and may take the form of simply ignoring it, accepting the default action offered by the system, or executing the user specified action. If the signal is blocked, meaning its handling is indefinitely deferred to a later time, it stays in a (blocked) pending state. This signal can later be unblocked to be handled by the process.

State transition diagram of a signal

It should be noted that some signals can neither be blocked nor be ignored. As a matter of fact, some—SIGKILL, for instance—cannot even be handled with a user-specified action.

Test Program[edit]

List_Test.c
  1 #include <assert.h>
  2 #include <setjmp.h>
  3 #include <signal.h>
  4 #include <stdio.h>
  5 #include <stdlib.h>
  6 
  7 #include "General.h"
  8 #include "Wrappers.h"
  9 #include "ds/ListExtended.h"
 10 #include "ds/ListIterator.h"
 11 
 12 void write_to_screen(const List this) {
 13   jmp_buf nxt_c;
 14   ListIterator itr = List_ListIterator(this, ITERATOR_FORWARD);
 15 
 16   printf("<%i> <head: ", List_Size(this));
 17   for (; ListIterator_HasNext(itr); ) {
 18     printf("%i", Integer_Value((Integer) ListIterator_Next(itr, nxt_c)));
 19     if (ListIterator_HasNext(itr)) printf(", ");
 20   } /* end of for(; ListIterator_HasNext(itr);) */
 21   printf(" :tail>\n");
 22 } /* end of void write_to_screen(const List) */
 23 
 24 void test_extended_removal(const List l) {
 25   int n;
 26   jmp_buf rac, rfc, rlc, rNec, rNfc;
 27 
 28   printf("\nTESTING [EXTENDED] REMOVE OPERATIONS\n");
 29   List_RemoveLast(l, Integer_Create(8), rlc);
 30   printf("Removing the last 8...\n"); write_to_screen(l);
 31 
 32   List_RemoveFirst(l, Integer_Create(5), rfc);
 33   printf("Removing the first 5...\n"); write_to_screen(l);
 34 
 35   List_RemoveNthFromEnd(l, Integer_Create(7), 3, rNfc);
 36   printf("Removing the third 7 from the end...\n"); write_to_screen(l);
 37 
 38   List_RemoveNthFromFront(l, Integer_Create(5), 2, rNfc);
 39   printf("Removing the second 5...\n"); write_to_screen(l);
 40 
 41   n = List_RemoveAll(l, Integer_Create(6), rac);
 42   printf("Removing all 6's...%i 6's removed.\n", n); write_to_screen(l);
 43 
 44   n = List_RemoveAll(l, Integer_Create(7), rac);
 45   printf("Removing all 7's...%i 7's removed.\n", n); write_to_screen(l);
 46 } /* end of void test_extended_removal(const List) */
 47 
 48 void test_extended_insertion(const List l) {
 49   jmp_buf iafc, ialc, ibfc, iblc, iaNc, ibNc;
 50 
 51   printf("\nTESTING [EXTENDED]INSERT OPERATIONS\n");
 52   List_InsertBeforeFirst(l, Integer_Create(5), Integer_Create(6), ibfc);
 53   printf("Inserting 5 before first 6...\n"); write_to_screen(l);
 54 
 55   List_InsertAfterFirst(l, Integer_Create(5), Integer_Create(6), iafc);
 56   printf("Inserting 5 after first 6...\n"); write_to_screen(l);
 57 
 58   List_InsertBeforeLast(l, Integer_Create(6), Integer_Create(5), iblc);
 59   printf("Inserting 6 before last 5...\n"); write_to_screen(l);
 60 
 61   List_InsertAfterLast(l, Integer_Create(7), Integer_Create(6), ialc);
 62   printf("Inserting 7 after last 6...\n"); write_to_screen(l);
 63 
 64   List_InsertAfterLast(l, Integer_Create(7), Integer_Create(36), ialc);
 65   printf("Inserting 7 after last 36...\n"); write_to_screen(l);
 66 
 67   List_InsertAfterLast(l, Integer_Create(8), Integer_Create(7), ialc);
 68   printf("Inserting 8 after last 7...\n"); write_to_screen(l);
 69 
 70   List_InsertAfterNth(l, Integer_Create(6), Integer_Create(5), 2, iaNc);
 71   printf("Inserting 6 after the second 5...\n"); write_to_screen(l);
 72 
 73   List_InsertBeforeNth(l, Integer_Create(6), Integer_Create(5), 3, ibNc);
 74   printf("Inserting 6 before the third 5...\n"); write_to_screen(l);
 75 } /* end of void test_extended_insertion(const List) */
 76 
 77 void test_iterators(const List l) {
 78   ListIterator itr;
 79   jmp_buf li_nc, li_pc;
 80 
 81   printf("\nTESTING THE FORWARD ITERATOR\n");
 82   itr = List_ListIterator(l, ITERATOR_FORWARD);
 83   for (; ListIterator_HasNext(itr); )
 84     printf("%i ", Integer_Value((Integer) ListIterator_Next(itr, li_nc)));
 85   ListIterator_Destroy(&itr);
 86   printf("\n");
 87 
 88   printf("\nTESTING THE BACKWARD ITERATOR\n");
 89   itr = List_ListIterator(l, ITERATOR_BACKWARD);
 90   for (; ListIterator_HasPrevious(itr); )
 91     printf("%i ", Integer_Value((Integer) ListIterator_Previous(itr, li_pc)));
 92   ListIterator_Destroy(&itr);
 93   printf("\n");
 94 } /* end of test_iterators(const List) */
 95 
 96 void test_empty_list(const List l) {
 97   jmp_buf gfc, glc, rac, rec, rfc, tac;
 98 
 99   assert(List_IsEmpty(l));
100   printf("Working on the following [empty!] list...\n");
101   write_to_screen(l);
102 
103   printf("Trying to get the first item...");
104   switch(setjmp(gfc)) {
105     case 0: List_GetFirst(l, gfc); break;
106     case EXC_LIST_EMPTY: printf("List is empty!!!\n"); break;
107   } /* end of switch(setjmp(gfc)) */
108 
109   printf("Trying to get the last item...");
110   switch(setjmp(glc)) {
111     case 0: List_GetLast(l, glc); break;
112     case EXC_LIST_EMPTY: printf("List is empty!!!\n"); break;
113   } /* end of switch(setjmp(glc)) */
114 
115   printf("Trying to remove the first [head] item...");
116   switch(setjmp(rfc)) {
117     case 0: List_RemoveFromFront(l, rfc); break;
118     case EXC_LIST_EMPTY: printf("List is empty!!!\n"); break;
119   } /* end of switch(setjmp(rfc)) */
120 
121   printf("Trying to remove the last [tail] item...");
122   switch(setjmp(rec)){
123     case 0: List_RemoveFromEnd(l, rec); break;
124     case EXC_LIST_EMPTY: printf("List is empty!!!\n"); break;
125   } /* end of switch(setjmp(rec)) */
126 
127   printf("Trying to convert to array...");
128   switch(setjmp(tac)) {
129     case 0: List_ToArray(l, tac); break;
130     case EXC_LIST_EMPTY: printf("List is empty!!!\n"); break;
131   } /* end of switch(setjmp(tac)) */
132 
133   printf("Trying to remove all 18's...");
134   switch(setjmp(rac)) {
135     case 0: List_RemoveAll(l, Integer_Create(18), rac); break;
136     case EXC_LIST_EMPTY: printf("List is empty!!!\n"); break;
137   } /* end of switch(setjmp(rac)) */
138   printf("\n");
139 } /* end of void test_empty_list(const List) */
140 
141 void test_nonempty_list(const List l) {
142   int n;
143   jmp_buf iafc, ialc, iaNc, ibfc, iblc, ibNc;
144   jmp_buf rac, rfc, rlc, rNec, rNfc;
145 
146   printf("Working on the following list...\n"); write_to_screen(l);
147 
148   printf("Trying to insert 5 after first 18...");
149   switch(setjmp(iafc)) {
150     case 0:
151       List_InsertAfterFirst(l, Integer_Create(5), Integer_Create(18), iafc); break;
152     case EXC_LIST_NOSUCHITEM: printf("No such item!!!\n");
153   } /* end of switch(setjmp(iafc)) */
154 
155   printf("Trying to insert 5 after last 18...");
156   switch(setjmp(ialc)) {
157     case 0:
158       List_InsertAfterLast(l, Integer_Create(5), Integer_Create(18), ialc); break;
159     case EXC_LIST_NOSUCHITEM: printf("No such item!!!\n");
160   } /* end of switch(setjmp(ialc)) */
161 
162   printf("Trying to insert 5 after the third 18...");
163   switch(setjmp(iaNc)) {
164     case 0:
165       List_InsertAfterNth(l, Integer_Create(5), Integer_Create(18), 3, iaNc); break;
166     case EXC_LIST_NOSUCHITEM: printf("No such item!!!\n");
167   } /* end of switch(setjmp(iaNc)) */
168 
169   printf("Trying to insert 5 before first 18...");
170   switch(setjmp(ibfc)) {
171     case 0:
172       List_InsertBeforeFirst(l, Integer_Create(5), Integer_Create(18), ibfc); break;
173     case EXC_LIST_NOSUCHITEM: printf("No such item!!!\n");
174   } /* end of switch(setjmp(ibfc)) */
175 
176   printf("Trying to insert 5 before last 18...");
177   switch(setjmp(iblc)) {
178     case 0:
179       List_InsertBeforeLast(l, Integer_Create(5), Integer_Create(18), iblc); break;
180     case EXC_LIST_NOSUCHITEM: printf("No such item!!!\n");
181   } /* end of switch(setjmp(iblc)) */
182 
183   printf("Trying to insert 5 before the third 18...");
184   switch(setjmp(ibNc)) {
185     case 0:
186       List_InsertBeforeNth(l, Integer_Create(5), Integer_Create(18), 3, ibNc); break;
187     case EXC_LIST_NOSUCHITEM: printf("No such item!!!\n");
188   } /* end of switch(setjmp(ibNc)) */
189 
190   printf("Trying to remove last 18...");
191   switch(setjmp(rlc)) {
192     case 0: List_RemoveLast(l, Integer_Create(18), rlc); break;
193     case EXC_LIST_EMPTY: printf("List is empty!!!\n"); break;
194     case EXC_LIST_NOSUCHITEM: printf("No such item!!!\n");
195   } /* end of switch(setjmp(rlc)) */
196 
197   printf("Trying to remove first 18...");
198   switch(setjmp(rfc)) {
199     case 0: List_RemoveFirst(l, Integer_Create(18), rfc); break;
200     case EXC_LIST_EMPTY: printf("List is empty!!!\n"); break;
201     case EXC_LIST_NOSUCHITEM: printf("No such item!!!\n");
202   } /* end of switch(setjmp(rfc)) */
203 
204   printf("Trying to remove third 6...");
205   switch(setjmp(rNfc)) {
206     case 0:
207       List_RemoveNthFromFront(l, Integer_Create(6), 3, rNfc); break;
208     case EXC_LIST_EMPTY: printf("List is empty!!!\n"); break;
209     case EXC_LIST_NOSUCHITEM: printf("No such item!!!\n");
210   } /* end of switch(setjmp(rNfc)) */
211 
212   printf("Trying to remove third 6 from end...");
213   switch(setjmp(rNec)) {
214     case 0:
215       List_RemoveNthFromEnd(l, Integer_Create(6), 3, rNec); break;
216     case EXC_LIST_EMPTY: printf("List is empty!!!\n"); break;
217     case EXC_LIST_NOSUCHITEM: printf("No such item!!!\n");
218   } /* end of switch(setjmp(rNec)) */
219 
220   printf("Trying to remove all 49's...");
221   switch(setjmp(rac)) {
222     case 0:
223       n = List_RemoveAll(l, Integer_Create(49), rac); 
224       assert(List_RemoveAll(l, Integer_Create(49), rac) == 0);
225       break;
226     case EXC_LIST_EMPTY: printf("List is empty!!!\n");
227   } /* end of switch(setjmp(rac)) */
228   printf("%i 49's removed.\n", n);
229 } /* void test_nonempty_list(const List) */
230 
231 void unexpected_cond(int signal_no) {
232   if (signal_no != SIGUSR1) 
233     fprintf(stderr, "Unexpected signal: %d\n", signal_no);
234     else fprintf(stderr, "User signal(SIGUSR1)!\n");
235 
236   exit(signal_no);
237 } /* void unexpected_cond(int) */
238 
239 int main(void) {
240   int i = 0;
241   jmp_buf gfc, glc, lac, rec, rfc;
242   List_Component_Funcs int_funcs = 
243     { &Integer_CompareIntegers, &Integer_CopyInteger, &Integer_DestroyInteger };
244   Object obj_array[] =
245     { Integer_Create(16), Integer_Create(25), Integer_Create(36), Integer_Create(49) };
246   Integer* corr_array;
247   List l1 = List_Create(DEFAULT, int_funcs);
248   List empty_list = List_Create(COPY, l1);
249   List l2 = List_Create(FROM_ARRAY, int_funcs, obj_array, 4);
250   List merged_list;

Next line of code makes a claim about our program: l2, which was previously created from an array of four elements, has a size of four. Otherwise would mean a semantic error in the constructor or the List_Size function and must be rectified before the List module hits the market.

If our claim is falsified by the program state assert will give rise to an abortion with a diagnostic message written on the standard output, which includes the "stringified" version of the claim together with the file name and line number.

Example: Implement in C a scheme that ensures out-of-bounds index values will not be silently ignored.

... 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); ... ...

This, of course, comes with a price: each time an assertion is seen extra time is spent for verification of the claim, which implies code full of asserts will run slower. Given that a production quality code is not only expected to be fault-free but also fast, having asserts in a finalized project simply does not make sense. So, what? Are we supposed to remove all of the assertions—probably tens of them—just to put them back in case maintenance is required? Not really! Defining the NDEBUG macro in our file or at the command line as a compile-time switch will solve the problem.

252   assert(List_Size(l2) == 4);
253 
254   for (; i < 10; i +=2) List_InsertInFront(l1, Integer_Create(i));
255   for (i = 1; i < 10; i +=2) List_InsertAtEnd(l1, Integer_Create(i));
256   printf("First list: "); write_to_screen(l1);
257   printf("Second List: "); write_to_screen(l2);
258 
259   printf("Merging the lists...\n");
260   merged_list = List_Merge(l1, l2); 
261   printf("Merged List: "); write_to_screen(merged_list);

Yet another mechanism for handling unexpected conditions: signals. Originally a UNIX concept, signals are used to inform processes—that is, running programs—about the occurrence of [usually] asynchronous events, such as an invalid memory access or an attempt to divide by zero.

Apart from triggering a signal by the error-detection mechanism of the computer or an action external to the program, one may also "raise" a signal explicitly by using the raise function. All these cause the relevant signal to be sent to the process.[10] Once a signal is raised—or triggered in some way—it needs to be handled. This is done by calling the handler of the signal.[11]

That’s all fine but how do we change the reaction of our program to a particular signal? After all, upon occurrence of the same signal, we may sometimes be able to modify the environment and let the program continue while at other times we must simply abort the program. We can achieve this by changing the handler function, which we do by the signal function. Associating a new handler with the signal, this function returns a pointer to the previous handler.[12] From this point on, unless another call to signal re-modifies the handler, all relevant signals will end up being processed in this new function.

In establishing a handler one can pass two special values as the second argument to signal: SIG_IGN and SIG_DFL. The former means the signal passed in the first argument is to be ignored whereas the latter means the signal will receive its default handling.

262   if (List_Size(merged_list) != List_Size(l1) + List_Size(l2)) {
263     void (*previous_handler)(int) = signal(SIGUSR1, &unexpected_cond);    
264     raise(SIGUSR1);
265     signal(SIGUSR1, previous_handler);
266   } /* end of if(List_Size(merged_list) != List_Size(l1) + List_Size(l2)) */
267 
268   printf("First item of the merged list: %i\n", Integer_Value((Integer)List_GetFirst(merged_list, gfc)));
269   printf("Last item of the merged list: %i\n", Integer_Value((Integer)List_GetLast(merged_list, glc)));
270   printf("Removing the first and last items in the merged list...\n");
271   List_RemoveFromEnd(merged_list, rec);
272   List_RemoveFromFront(merged_list, rfc);
273 
274   write_to_screen(merged_list);
275 
276   corr_array = (Integer *) List_ToArray(merged_list, lac);
277   printf("Corresponding array...\n");
278   for (i = 0; i < List_Size(merged_list); i++)
279     printf("[%i] %i ", i, Integer_Value(corr_array[i]));
280   printf("\n");
281 
282   test_extended_insertion(merged_list);
283   test_extended_removal(merged_list);
284   test_iterators(merged_list);
285 
286   printf("\nTESTING EXCEPTIONAL CONDITIONS\n");
287   test_empty_list(empty_list);
288   test_nonempty_list(merged_list);
289 
290   return 0;
291 } /* end of int main(void) */
gcc –c List.c –ID:/include↵ # In Cygwin

For creating the executable during the test phase we should use the following command. This will effectively enable the assertion facility.

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

Getting rid of the semantic errors in the program—or at least hoping so—we can de-activate the assertion facility. This is done by defining the NDEBUG macro.

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

Code Reuse through Composition[edit]

Interface[edit]

Stack.h
 1 #ifndef STACK_H
 2 #define STACK_H
 3 
 4 #include <setjmp.h>
 5 
 6 #include "General.h"
 7 
 8 #define EXC_STACK_EMPTY -41
 9 
10 struct STACK;
11 typedef struct STACK* Stack;
12 
13 typedef struct _Obj_funcs {
14   COMPARISON_FUNC compare_component;
15   COPY_FUNC copy_component;
16   DESTRUCTION_FUNC destroy_component;
17 } Stack_Component_Funcs;
18 
19 extern Stack Stack_Create(int constructor_type, ...);
20 extern void Stack_Destroy(Stack*);
21 
22 extern Object Stack_Peek(const Stack, jmp_buf spc) /* throws StackEmpty */ ;
23 extern Object Stack_Pop(const Stack, jmp_buf spc) /* throws StackEmpty */ ;
24 extern void Stack_Push(const Stack, Object);
25 extern BOOL Stack_IsEmpty(const Stack);
26 
27 #endif

Implementation[edit]

Stack.c
 1 #include <setjmp.h>
 2 #include <stdarg.h>
 3 #include <stddef.h>
 4 #include <stdio.h>
 5 #include <stdlib.h>
 6 
 7 #include "General.h"
 8 #include "ds/List.h"
 9 #include "ds/Stack.h"
10 
11 struct STACK { List underlying_list; };
12 
13 Stack Stack_Create(int type, ...) {
14   va_list ap;
15   List_Component_Funcs lst_funcs;
16   Stack_Component_Funcs funcs;
17   Stack existing_stack;
18   Stack ret_stack = (Stack) malloc(sizeof(struct STACK));
19 
20   if (!ret_stack) {
21     fprintf(stderr, "Out of memory...\n");
22     return(NULL);
23   } /* end of if(!ret_stack) */
24 
25   va_start(ap, type);
26   switch(type) {
27     case COPY:
28       existing_stack = va_arg(ap, Stack);
29       ret_stack->underlying_list = List_Create(COPY, existing_stack->underlying_list);
30       break;
31     case DEFAULT:
32       funcs = va_arg(ap, Stack_Component_Funcs);
33       lst_funcs.compare_component = funcs.compare_component;
34       lst_funcs.copy_component = funcs.copy_component;
35       lst_funcs.destroy_component = funcs.destroy_component;
36       ret_stack->underlying_list = List_Create(DEFAULT, lst_funcs);
37       break;
38   } /* end of switch(type) */
39   va_end(ap);
40 
41   return ret_stack;
42 } /* end of Stack Stack_Create(int, ...) */
43 
44 void Stack_Destroy(Stack* this) {
45   if (*this == NULL) return;
46 
47   List_Destroy(&(*this)->underlying_list);
48   free(*this);
49   *this = NULL;
50 } /* end of void Stack_Destroy(Stack*) */
51 
52 Object Stack_Peek(const Stack this, jmp_buf sp_c) /* throws Stack_Empty */ {
53   Object res;
54 
55   if (List_IsEmpty(this->underlying_list))
56     longjmp(sp_c, EXC_STACK_EMPTY);

Since by the time control reaches this point we will have assured the stack is not empty—by checking the underlying list as in the previous if statement—there is no point in passing a buffer, which is used to convey information about any problem. We therefore pass NULL as the second argument.

res = List_GetFirst(this->underlying_list, NULL);
58   return res;
59 } /* end of Object Stack_Peek(const Stack, jmp_buf) */
60 
61 Object Stack_Pop(const Stack this, jmp_buf sp_c) /* throws StackEmpty */ {
62   jmp_buf rfc;
63   Object res;
64 
65   if (setjmp(rfc) == EXC_LIST_EMPTY)
66     longjmp(sp_c, EXC_STACK_EMPTY);
67   res = List_RemoveFromFront(this->underlying_list, rfc);
68 
69   return res;
70 } /* end of Object Stack_Pop(const Stack, jmp_buf) */
71 
72 void Stack_Push(const Stack this, Object new_item) {
73   List_InsertInFront(this->underlying_list, new_item);
74 } /* end of void Stack_Push(const Stack, Object) */
75 
76 BOOL Stack_IsEmpty(const Stack this) {
77   return(List_IsEmpty(this->underlying_list));
78 } /* end of BOOL Stack_IsEmpty(const Stack) */

Notes[edit]

  1. ISO C requires EOF to be defined as a negative integral constant.
  2. This can certainly be avoided by removing the error handling, which is an invitation to a larger disaster.
  3. At its heart, exception handling is a service provided by the operating system. One should also add the compiler support, which generates the relevant code.
  4. First make sure you execute the batch file named vcvars32.bat found in bin subdirectory of the Microsoft Visual C/C++ compiler and then issue the command
    cl /w /FeExecName.exe CsourceName.c /link /SAFESEH:NO↵
    on the Visual Studio .NET Command Prompt and run the executable.
  5. TIB stands for Thread Information Block and holds information specific to the current thread.
  6. First partition of all Win32 processes is inaccessible to users. Trying to read from or write to this partition—whose size may vary with the operating system—causes an access violation to occur and gives rise to the termination of the process. This is a rather handy tool for dealing with NULL-pointer manipulation in C: If the NULL macro is defined to be zero or any address value within the limits of the first partition, without any interference from the compiler, any attempt to dereference a NULL-pointer will eventually lead to an access violation.
  7. Note we prefer clarity over conciseness. This block could certainly be written more concisely.
  8. You knew it would be machine-dependent! After all, as part of the state information we need to store a bunch of register values, which differ from architecture to architecture (both in number and size).
  9. This goes to support our view of handles as smart pointers.
  10. Note the similarity with throwing an exception.
  11. Very much like the body of a catch block.
  12. What does the default handler of SIGUSR1 do?