Programming Language Concepts Using C and C++/Exception Handling
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)
editFundamental 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]
#include <windows.h>
#include <stdio.h>
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.
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(.....) */
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; ...
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;
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.
__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
}
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.
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]
__asm {
MOV EAX, 0
MOV [EAX], 1
}
__asm {
MOV EAX, 6
MOV EDX, 0
MOV EBX, 0
DIV EBX
}
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.
__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");
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.</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!
Exception Handling in C Using Microsoft Extensions
editUsing Microsoft extensions you can write [non-portable] C programs with exception handling, albeit somewhat differently. The following is a simple example to this.
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
int *iptr = 0;
int zero = 0;
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.
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) {
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.
__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);
Instead of a plain filter, next __except
has a filter function, which serves exactly the same purpose.
__try { *iptr = 0; }
__except(av_filter(GetExceptionCode())) { }
printf(“ *iptr: %d\n”, *iptr);
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.
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↵
- ...
- ...
- ...
Module
editInterface
edit#ifndef LIST_H
#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.
#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
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.
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
#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
#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
Implementation
edit#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
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.
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.
struct LIST {
COMPARISON_FUNC compare_component;
COPY_FUNC copy_component;
DESTRUCTION_FUNC destroy_component;
unsigned int size;
struct _LIST* head;
};
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]
struct _LIST {
Object info;
struct _LIST* next;
struct _LIST* prev;
};
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.
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,
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
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.
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
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.
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;
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
.
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 alongjmp
function. Doing so will (in addition to unwinding the runtime stack) return control to the point wheresetjmp
function was called. Pretend as ifsetjmp
were not called before and return as its result the value that was returned in the second argument of thelongjmp
function.
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) */
Robustness and Correctness of a Program
editIn 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
editAssertion 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
editSignals 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. UnlikeSIGKILL
, 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
andSIGUSR2
- 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.
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#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;
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 assert
s will run slower. Given that a production quality code is not only expected to be fault-free but also fast, having assert
s 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.
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);
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.
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↵ # 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
editInterface
edit#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
Implementation
edit#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);
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);
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) */
Notes
edit- ↑ ISO C requires
EOF
to be defined as a negative integral constant. - ↑ This can certainly be avoided by removing the error handling, which is an invitation to a larger disaster.
- ↑ 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.
- ↑ 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↵
- ↑ TIB stands for Thread Information Block and holds information specific to the current thread.
- ↑ 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 theNULL
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 aNULL
-pointer will eventually lead to an access violation. - ↑ Note we prefer clarity over conciseness. This block could certainly be written more concisely.
- ↑ 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).
- ↑ This goes to support our view of handles as smart pointers.
- ↑ Note the similarity with throwing an exception.
- ↑ Very much like the body of a catch block.
- ↑ What does the default handler of
SIGUSR1
do?