Thou Shalt Not Assert
Everyone says that checking return codes from functions is always a good idea. But sometimes you don’t know what to do when an error occurs. Here is an example:
The Windows CloseHandle
function returns FALSE
when the function fails. From MSDN documentation:
If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To get extended error information, call
GetLastError
.
That’s the extent of the information you get and there is no clue as to why the function would fail1. So, being overly cautious, I wrote a piece of code like this:
//...
assert (CloseHandle (h));
//...
My thinking was that any eventual failure of CloseHandle
will give an error message in debug mode and I would be able to investigate and see where it’s coming from. In release mode nothing would happen and anyway I wouldn’t know what action to take. Bothering the user with an obscure error message didn’t seem useful.
I debugged the program and sent out the release version but, to my surprise, my program was leaking handles in release mode. Everything was wonderful in debug mode but once I switched to release mode, it would start leaking handles. Debugging in release mode is not a pleasant experience as local variables disappear and statements get rearranged by the optimizer. In the end I traced the problem to the statement above and suddenly I had a flash: assert
macro gets replaced with nothing in release mode. From cppreference.com:
#ifdef NDEBUG
# define assert(condition) ((void)0)
#else
# define assert(condition) /*implementation defined*/
#endif
In release mode, when NDEBUG
macro is defined, the call to CloseHandle
simply disappears and handles start to leak.
It was easy to fix my leak once I figured out the cause but I remain with two nagging questions:
-
Is it a good practice to check returned values even when you don’t know what to do in case of failure?
-
Would it not be better to have a definition of
assert
that allows for side-effects. Something like:
#ifdef NDEBUG
# define assert(condition) (condition)
#else
# define assert(condition) /*implementation defined*/
#endif
-
Not exactly correct: MSDN goes on to say that: “If the application is running under a debugger, the function will throw an exception if it receives either a handle value that is not valid or a pseudo-handle value.” One can guess that, if not running under a debugger, the function will just fail and return
FALSE
. ↩︎