3 ms·
Exactly. Writing libraries in C means you have to be much more strict than simple throwaway single execution binaries. What our company has ended up doing is w
by alexkus 13y ago
Exactly. Writing libraries in C means you have to be much more strict than simple throwaway single execution binaries.
What our company has ended up doing is wrappering most low level functions with the common code around it. For example, fopen() may fail and return EINTR if the process received a signal just as it was opening a file (we use signals to tell processes to reread their config so this does happen albeit rarely) so the wrapper calls fopen() in a do/while loop that repeats on EINTR (and a few others). It saves you from writing the same 50 odd lines each time you want to open a simply open a file.
In an exception led world you'd still have to do the same thing, but with different syntactic sugar. You can't just fail and bubble up the exception without dealing with the few exceptions you must deal with and repeat, and you can't just repeat on every exception as most will just fail again and again. You end up writing code to do the same thing just in different styles.
Checking the return values of every single function call, and dealing with it, can make the code verbose (one line of code and then 10+ lines dealing with errors) but it is worth it in the end for bullet proof programs. Also code where you have to undo all of the work already done in the function up until that point, which can get a little tedious, i.e.:-
APP_RET makefoo( char *fname )
{
char *foo,*bar;
thingy_t *qux;
size_t len;
ASSERT( fname );
len = strlen( fname );
foo = malloc( len+1 );
if( foo == NULL ) {
return( ENOMEM );
}
bar = malloc( (2*len)+1 );
if( bar == NULL ) {
free( foo );
return( ENOMEM );
}
qux = malloc( sizeof( thingy_t ) );
if( qux == NULL ) {
free( foo );
free( bar );
return( ENOMEM );
}
...
return( APP_OK );
}
It's the job of anything calling makefoo() to deal with the various errors it can return, but the idea is to return the minimum number of unique error codes as necessary to avoid proliferation of error codes to the higher and higher functions. Many calling functions will only really care about success or failure, and will just use the return code to log out the reason for the failure.
The wrappers help deal with many situations; did fwrite() write all of the bytes we wanted or just some of them? Well, our wrapper around fwrite will handle short writes and repeat the call depending on the result of ferror().
- asveikau 13y ago> Also code where you have to undo all of the work already done in the function up until that point, which can get a little tedious, i.e.:- Sorry pal, you're doing it wrong. At least, this is not the way I've done it and seen it done in large C code bases. You're supposed to have only one return statement, and one block that frees everything. For example if you initialize `foo`, `bar`, and `qux` to `NULL`. Then testing these pointers for `NULL` de facto tells you how far you got in the function, and which buffers need to be freed. Just before your one single return statement (can't emphasize this enough) you call `free` on all of them regardless of success or failure. It's much more composeable than what you described - allocations for `foo`, `bar`, `qux` can fail, the ones that will be not yet allocated at that point in time will be `NULL`, and `free(NULL)` is a harmless no-op. None of this business of "I've got to return now, let's see, how many of these buffers do I need to release at this point in time?", with varying amounts of the same free statement appearing redundantly. Write the cleanup block once when the pointers are about to fall out of scope, have it able to run in both success and failure cases and be done with it. Think of it as a more manual RAII if you like. As for what to replace those early `return`s with, the two common schools are `goto` into the cleanup block, or repeatedly checking some kind of failure status variable before performing new actions.
- Nursie 13y agoWhat you've described looks just as messy to my eyes, and I've seen all different ways to do this. You can nest under if(thing!=NULL) but then you end up with indentation creep. You can use the goto pattern if you like but some folks will tell you that goto's are never, ever to be used. Or you can do what's done above. When it comes down to it the logic is basically identical and it's just down to code formatting.
- seabee 13y agoThe logic may be identical but the scope for programmer error is not. Consider the possible mistakes someone can write by doing it each different way.
- 13y ago