Exceptions: A Tale of Love and Hate
I am very much a "C" style programmer, so exceptions are not something I use, or see on an everyday basis. Despite this, I've always had a love/hate relationship with exceptions, with my emotions depending on the situation I'm in.
In the past few years, I've used OCaml and Python on occasion to do some quick prototyping (and even some not-too-quick prototyping), and I found the languages' support for exceptions to be quite useful. Being able to call library functions without having to continuously check their return values, and instead be able to rely on the called functions to throw an exception when something bad happens, allows me to write much more quickly. Compare this, for example, to OpenCL in C (because C doesn't have exceptions), which I've been exploring recently. Even the most basic of OpenCL programs, to multiply two vectors, requires 50+ lines of boilerplate code, half of which is "if" statements checking to make sure a cl function didn't return -1. When I was writing my first OpenCL program, I was muttering in annoyance with how much boilerplate cruft I had to spew out and dreaming of exceptions. What I wanted, was for the cl functions to throw an exception on error, which would then allow me to have a single generic catch-all exception handler to dump some error messages and gracefully die.
But we shouldn't forget that exceptions have a place outside of simple prototyping also. Some level of terseness is always appreciated, especially when it allows the programmer to focus on the real task at hand, instead of writing an error check for every function call.
The real magic of exception handlers is that they allow the program to unwind the call stack all in one go, without actually having to jump to each and every return address on the stack as it goes. This is quite powerful, allowing the program to immediately handle the exception when it occurs, and removing the need for the programmer to organize her code to pass the error back up the call stack to where it can be processed.
But as Uncle Ben reminded us, with great power comes great responsibility. The power of exception handlers also gives programmers the ability to screw things up in remarkable ways. Because the exception basically forces a jump to the closest exception handler, all the code in between the exception and the handler won't get executed. This may include important bits like free() and close(), or even just simple things like finishing up printing parts of an output message. While the exception handler is *supposed* to handle all those bits, it's not uncommon for them to be forgotten. A common reason for this, is placing the exception handler very far away from where the exception might be thrown.
Which leads me nicely into my next story. I was working on a bug in some Python code last week, where an exception was thrown somewhere within several thousand lines of code and then handled way at the top of the program. The error message printed was "Exception occurred in <wubwubwub> server," making it quite possibly one of the most useless error messages of all time. I spent a good five minutes raging silently at the programming gods before trying to identify a root cause. It's important to remember that the point of exception handlers is to improve our lives not just in the short term, but also for the long term when code needs to be fixed and maintained.
There are of course many other reasons to love or hate exceptions. But these are the ones that strike a particular chord with me because of my limited experiences. I don't believe exceptions are totally bad, it is merely a powerful tool that is far too often misused, much like "goto" (let's not get ourselves muddled in that argument though...). My personal belief is that the power and terseness of exceptions is sufficient enough that it shouldn't be completely eschewed, but simply used sparingly and with great care. The problem with that idea of course, is that software development sits in the real world, not some lovey-dovey happy-place, where there is always plenty of time to work on projects, everyone is properly trained, all code is properly reviewed, and vampires sparkle in the sun.
GNU Readline
I reccently wrote a shell for a project in my CS class. One of the advanced features that my partner and I implmented in the shell, was tabbed completion, and in order to implemet this extremely useful shell feature, we used the GNU readline library. The GNU Readline library is a beast, and not in the good way. Its a great hulking pile of code and documentation, intended to provide a ton of features for reading typed lines. Once you figure out what all the function pointers, bindings, and generators in the Readline library are supposed to do, things become much more straightfoward, but it doesn't negate the fact that initially figuring out Readline is a bit of a pain in the butt.
The first thing I did after we were told that the Readline library could make our project easier to design, was to pop open a terminal and type "man readline". What I got was a basic summary of Readline, so in order to get the full library manual I had to resort to Google. I did however, happen to see this at the bottom of the manpage:
BUGS
It's too big and too slow.
Now if even the guys working on the Readline library think thats its too big and too slow, we may have a potential problem on our hands.
One of the plus sides of however Readline's enormity was that it does offer a whole slew of features, like a kill ring, and generators for tab-completion of filenames and usernames. It would be very nice though, if all these features could be implemented without the need for a manual that probably took me longer to read, then it did for me to code up a generator for command line completion.
A Simple Weird Sorter
This is a little toy program written for one of my CS classes. The program takes a positive integer N as an argument and reads from stdin until it gets an EOF. The program then sorts the input using qsort, while assuming that each element to be supported is N bytes long.
Fun facts: the output from this program looks kinda cool if you do "./program_name 1 < index.html" where index.html is some large webpage.
#include <stdio.h> #include <stdlib.h> #include <errno.h> #define MEM_SIZE 100 // for the sake of ease, token length and input array are global unsigned long global_token; char * in_ptr; // comparator for qsort, // returns 1 if a is bigger, -1 is smaller, 0 if equal int comparator(const void * a, const void * b) { unsigned int i; int r_value = 0; for(i = 0; i < global_token; ++i) { if( *(i + (char*)a) < *(i + (char*)b)) return -1; else if ( *(i + (char*)a) > *(i + (char*)b)) return 1; } return 0; } // get input from stdin and save it to dynmaically allocated memory unsigned long get_input() { char* temp_in_ptr; unsigned long ar_size, n_ar_size; unsigned long max_size = MEM_SIZE; for(ar_size = 0; !feof(stdin); ++ar_size) // loop until EOF { if(max_size == (1 + ar_size)) // check if more memory is needed { temp_in_ptr =(char *)realloc(in_ptr, (size_t)(MEM_SIZE + max_size)); if(!temp_in_ptr) // returns error and exits if realloc fails { fprintf(stderr, "Error in allocating memory for input."); fprintf(stderr, "Input data may be too large.\n"); free(in_ptr); // free dynamically allocated memory exit(1); } in_ptr = temp_in_ptr; max_size += MEM_SIZE; } *(in_ptr + ar_size) = fgetc(stdin); // get next char from input if(ferror(stdin)) // check for read error { fprintf(stderr, "Error encountered in reading input.\n"); free(in_ptr); // free dynamically allocated memory exit(1); } } ar_size --; // get rid of EOF char // if string was not multiple of token length, need to make it // a multiple of token length and fill end with '\0' if(ar_size % global_token != 0) { n_ar_size = (global_token * (unsigned int)(ar_size / global_token + 1)); if(n_ar_size > max_size) // check if more memory is needed { temp_in_ptr = (char *)realloc(in_ptr, (size_t)n_ar_size); if(!temp_in_ptr) // check for error in mem allocation { fprintf(stderr, "Error in allocating memory for input."); fprintf(stderr, "Input data may be too large.\n"); free(in_ptr); // free dynamically allocated memory exit(1); } in_ptr = temp_in_ptr; } for(; ar_size != n_ar_size; ++ar_size) // fill with '\0' { *(ar_size + in_ptr) = '\0'; } } return ar_size; } int main(int argc, char** argv) { unsigned long ar_size, i; in_ptr = (char *)malloc(MEM_SIZE * sizeof(char)); if (argc < 2) { fprintf(stderr, "Must provide at least one argument.\n"); free(in_ptr); // free dynamically allocated memory exit(1); } // use strtoul() to convert char to long int // and use errno to check for error in conversion errno = 0; global_token = strtoul(*(argv + 1), NULL, 0); if(!global_token || errno == ERANGE) { fprintf(stderr, "Argument must be a positive integer.\n"); free(in_ptr); // free dynamically allocated memory exit(1); } ar_size = get_input(); // get data from stdin // sort with qsort qsort(in_ptr, (size_t)(ar_size / global_token), (size_t)(sizeof(char) * global_token) , comparator); for(i = 0; i < ar_size; ++i) //print sorted data { printf("%c", *(in_ptr + i)); } free(in_ptr); // free dynamically allocated memory return 0; }
Passing an Array in C
This quick little article is intended to give you a better idea of how arrays are passed in C. A basic understanding of pointers is required. If you don't know what pointers are, check them out at wikipedia.
We'll be using the following array as an example throughout the article:
char c_array[50];
The variable that we use to reference the array is actually a pointer. A pointer points to a location in the computer's memory, as you probably already know. You can have pointers to any data type you want, even a pointer to a pointer. The reason why we can reference all 50 elements of our array with just one pointer, is because arrays are allocated as one big fat chunk of memory. That is, all the elements in the array are lined up in the computer's memory one after the other. This is very handy, because if we know the location of the zero element in the array (the pointer to an array points to the zero element) we can get the physical location in memory of the fifth element (I made a funny!) by doing something like:
c_array + 4
Of course, thats a rather impractical way of accessing data, so we usually just end up doing:
c_array[4]
(Notice that I used a 4 to access the fifth element, because in Computer Science we always begin counting from 0, and not 1)
The fact that arrays are referenced by pointers, can be both good and bad. If your array is quite large, then passing by value can be quite expensive (that is, if you could pass an array by value, arrays in C have to be passed by reference). However, as you probably know, passing by reference can be a problem because you may want to leave your old array untouched. When passing an array, if you want to make sure your passed array isn't changed or modified you can either pass it as const, or else create a new array and copy all your old data into it. You can use strcpy from the standard library to copy the array into a new one.
Dynamically Create a Multidimensional Array
I had a few problems trying to dynamically create and pass a multidimensional array in C++ for my previous programming project. Of course, passing an array by value is rather ridiculous when the array is large, so I had to pass by reference. Now, passing a single dimensional array is really quite simple in C++, you just do the following:
//stuff
int input_cols;
char *char_array_ptr;
//stuff
cout << "cols? ";
cin >> input_a;
char_array_ptr = new char[input_cols];
return char_array_ptr;
And hey, you're done. The same thing could pretty much be done in C by using malloc, instead of new. But as I mentioned, I needed to pass a multidimensional array.