Skip to content

Instantly share code, notes, and snippets.

@niuk
Created July 20, 2011 18:27
Show Gist options
  • Save niuk/1095566 to your computer and use it in GitHub Desktop.
Save niuk/1095566 to your computer and use it in GitHub Desktop.
Resource Handling, PART I
/* Say you want to read some stuff into a buffer.
* To do so in C, a (very) naive programmer would write something like: */
void *read_buf_from_file(char *path) {
void *buf = malloc(512);
int fd = open(path, O_RDONLY);
read(fd, buf, 512);
return buf;
}
/* That is very bad code. It doesn't do any error checking, so
* if any one of malloc, open or read failed, you're boned. Also,
* it leaks the opened file descriptor so that needs to be rectified.
* Thus, C programmers usually dutifully wrap their library calls in
* error checking conditionals, like so: */
void *read_buf_from_file(char *path) {
void *buf = malloc(512);
if (buf == NULL) return NULL;
int fd = open(path, O_RDONLY);
if (fd < 0) return NULL;
if (read(fd, buf, 512) < 0) return NULL;
close(fd);
return buf;
}
/* Slightly better, but still problematic. The first return is okay.
* The second one would result in a memory leak, and the third one would
* cause both a memory leak and a leaked file descriptor. Thus, C programmers
* also have to make sure every resource is freed even in the case of an error: */
void *read_buf_from_file(char *path) {
void *buf = malloc(512);
if (buf == NULL) return NULL;
int fd = open(path, O_RDONLY);
if (fd < 0) {
free(buf);
return NULL;
}
if (read(fd, buf, 512) < 0) {
free(buf);
close(fd);
return NULL;
}
close(fd);
return buf;
}
/* This code pretty much handles every case (except one, but
* we'll get to that later). So now the main problem is aesthetics.
* Note that both "free(buf)" and "close(fd)" are each written twice.
* If the number of resources increases, they might be repeated many,
* many times. Repeating yourself is always bad and almost always avoidable.
* Another, better approach would be: */
void *read_buf_from_file(char *path) {
void *buf = NULL;
int fd = open(path, O_RDONLY);
if (fd < 0) goto e0;
buf = malloc(512);
if (buf == NULL) goto e1;
if (read(fd, buf, 512) < 0) goto e2;
goto e1;
e2: free(buf);
buf = NULL;
e1: close(fd);
e0: return buf;
}
/* This kind of code, despite the use of gotos (which are Considered Harmful)
* is actually quite idiomatic in some settings. The resource management code
* is not duplicated because there is only one exit point for the function.
* Furthermore, the resources are decallocated in the reverse order of their
* allocation, which is desirable for cases where dependencies need to be
* observed. However, gotos are somewhat distasteful, and this code would
* stop working if we introduce exceptions, which can cause functions to exit
* at pretty much any point. Imagine that open, malloc and read throw
* exceptions instead of returning an error value: */
void *read_buf_from_file(char *path) {
void *buf = malloc(512);
// If malloc throws an exception, we're still okay.
int fd = open(path, O_RDONLY);
// If open throws an exception, we got a memory leak.
read(fd, buf, 512);
// If read throws an exception, we have both the memory leak
// and a file descriptor leak.
return buf;
}
/* In this imaginary language, there is no clear way to avoid the leaks,
* except by using try/catch. But to handle every possible error case, the
* function has to catch every possible exception, which kind of defeats the
* point of having exceptions in the first place. Furthermore, the code needed
* for this would be so verbose, I'm not even gonna write it out. */
/* C++ solves this problem by having destructors on objects that get called
* every time the object goes out of scope. Destructors are extremely useful,
* but they require wrapping resources in classes, which is itself a verbose
* affair. The code above would look something like the following: */
class Buffer {
void *buf;
...
}
class File {
int fd;
...
}
void *read_buf_from_file(char *path) {
Buffer buf = new Buffer(512);
File fd = new File(path, File::READONLY);
File.read(buf, 512);
return buf;
}
/* Very pretty, if we ignore the wrapper classes needed. However, there is
* a problem we need to address in File's destructor: */
File::~File() {
if (close(fd) < 0) {
throw new FileCloseException();
}
}
/* The problem is that destructors are never supposed to throw exceptions.
* If the destructor was itself triggered by an exception, then throwing a
* new one would cause the runtime to lose one of the two pieces of information.
* Both of them, however, are needed for the program and its programmer to know
* what the hell went on. Currently, no language solves this problem elegantly. */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment