Thou Shalt Not Assert

Thou Shalt Not Assert

October 1, 2022
Programming, Testing

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:

  1. Is it a good practice to check returned values even when you don’t know what to do in case of failure?

  2. 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

  1. 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↩︎