Hi guys,
I'd like to get peoples' ideas on how to handle errors in C, a language without any built-in 'try-catch' type of framework. I'll begin by describing what we (me+labgroup) do now and what I think is good/bad about it. Our project is called "ProjectX"; it's a computational fluids package.
So at the moment, every function in the entire code returns an error code. Things like "NO_ERROR", "BAD_INPUT", "SEARCH_NOT_FOUND", "NON_PHYSICAL", etc.
Then almost anytime you call one of the ProjectX functions, you wrap the call with a macro:
Which will get 'translated' to:
The extra {} brackets are there so that "_ierr" cannot interfere with any previous declarations. We could also just write "_ierr = ..." and depend on every function to have a local "_ierr" variable, but that seems cumbersome.
When the error is not catastrophic, you might write:
But the idea is that whenever a catastrophic error code is returned by a function, the code will propagate upward all the way to main. Then since the PXErrorReturn macro will print at every level, you get a 'call-stack' trace showing you where the error occurred & how you got there.
So advantages:
-easy to use, people are used to it & use it consistently
-prints out a full call-stack trace, displays information about what specific error code occured
disadvantages:
-not easy to turn off
-does not "clean up" when exiting--once an error code occurs, the current function returns. Then the caller returns; then the caller's caller returns, etc. So if there are malloc() calls in the functions of that sequence (e.g., if myfunc allocates space to a pointer on its own stack), the corresponding free() calls never happen.
The last point is mostly annoying in our unit testing framework. Unit tests check the failure conditions for functions, so when free() isn't called and the stack is thrown out after return, valgrind reports lost memory.
We try to ensure that at least our unit tests have clean valgrind reports--no errors, no leaks. And it's pretty annoying/impossible to have to remember "oh this leak is OK b/c it is associated with an error condition."
Is it possible to change this behavior? An obvious choice would be to have appropriate free() calls before returning an error code. But this would lead to a lot of code duplication... and duplication of code that runs very rarely at that. I was thinking to do
Additionally, a ton of our error checks are related to input sanitization. These issues really only arise when the developer screwed up a function call or when the input file is misconfigured. It seems like in the former case, the C assert() built-in would be much better. Even better, assert() is really easy to turn off. So the PXErrorReturn system would be reserved for errors like "the current solution is non-physical" whereas assert() would be used for "this input pointer is not null". The difference being that in the first case, any user would want to know about a non-physical solution, whereas in the second case, all such errors "should" be impossible outside of development.
The only downside of assert() is that it doesn't automatically provide that call-stack trace. So people here will balk at it I'm sure, lol. But is it considered more "proper" to have assert() for pre/post condition + invariant checks? Then you could just invoke a debugger and place a breakpoint on the offending assert().
Thoughts?
-Eric
I'd like to get peoples' ideas on how to handle errors in C, a language without any built-in 'try-catch' type of framework. I'll begin by describing what we (me+labgroup) do now and what I think is good/bad about it. Our project is called "ProjectX"; it's a computational fluids package.
So at the moment, every function in the entire code returns an error code. Things like "NO_ERROR", "BAD_INPUT", "SEARCH_NOT_FOUND", "NON_PHYSICAL", etc.
Then almost anytime you call one of the ProjectX functions, you wrap the call with a macro:
Code:
PXErrorReturn( PXSomeFunction(arg1,...) );
Which will get 'translated' to:
Code:
{
int _ierr = PXSomeFunction(arg1,...);
if(_ierr != PX_NO_ERROR){
printf("Error %s occured in file %s on line %d",...);
return _ierr;
}
}
When the error is not catastrophic, you might write:
Code:
_ierr = PXSomeFunction(arg1,...);
if(_ierr == PX_SEARCH_NOT_FOUND){
/* do something in response to search not found... like create the object */
}else if(_ierr != PX_NO_ERROR){
printf("Error %s occured in file %s on line %d",...);
return _ierr;
}
But the idea is that whenever a catastrophic error code is returned by a function, the code will propagate upward all the way to main. Then since the PXErrorReturn macro will print at every level, you get a 'call-stack' trace showing you where the error occurred & how you got there.
So advantages:
-easy to use, people are used to it & use it consistently
-prints out a full call-stack trace, displays information about what specific error code occured
disadvantages:
-not easy to turn off
-does not "clean up" when exiting--once an error code occurs, the current function returns. Then the caller returns; then the caller's caller returns, etc. So if there are malloc() calls in the functions of that sequence (e.g., if myfunc allocates space to a pointer on its own stack), the corresponding free() calls never happen.
The last point is mostly annoying in our unit testing framework. Unit tests check the failure conditions for functions, so when free() isn't called and the stack is thrown out after return, valgrind reports lost memory.
We try to ensure that at least our unit tests have clean valgrind reports--no errors, no leaks. And it's pretty annoying/impossible to have to remember "oh this leak is OK b/c it is associated with an error condition."
Is it possible to change this behavior? An obvious choice would be to have appropriate free() calls before returning an error code. But this would lead to a lot of code duplication... and duplication of code that runs very rarely at that. I was thinking to do
Additionally, a ton of our error checks are related to input sanitization. These issues really only arise when the developer screwed up a function call or when the input file is misconfigured. It seems like in the former case, the C assert() built-in would be much better. Even better, assert() is really easy to turn off. So the PXErrorReturn system would be reserved for errors like "the current solution is non-physical" whereas assert() would be used for "this input pointer is not null". The difference being that in the first case, any user would want to know about a non-physical solution, whereas in the second case, all such errors "should" be impossible outside of development.
The only downside of assert() is that it doesn't automatically provide that call-stack trace. So people here will balk at it I'm sure, lol. But is it considered more "proper" to have assert() for pre/post condition + invariant checks? Then you could just invoke a debugger and place a breakpoint on the offending assert().
Thoughts?
-Eric