Make your own assert()

Daniel Andersson
2022-06-08

For this blog entry we’ll have a trivial program, but keep a larger project in mind.

#include <assert.h>
int main(void)
{
  assert(1==2);
  return 0;
}

Running that in a debugger results in it stopping at

a.out: main.c:4: main: Assertion `1==2' failed.

Program received signal SIGABRT, Aborted.
0x00007ffff7e2134c in __pthread_kill_implementation () from /usr/lib/libc.so.6

While it says which line and file the assert happened, the debugger is deep in libc when it stops the program, which isn’t very ergonomic, because you have to look at the backtrace and jump to the frame of the assert:

(gdb) bt
#0  0x00007ffff7e2134c in __pthread_kill_implementation () from /usr/lib/libc.so.6
#1  0x00007ffff7dd44b8 in raise () from /usr/lib/libc.so.6
#2  0x00007ffff7dbe534 in abort () from /usr/lib/libc.so.6
#3  0x00007ffff7dbe45c in __assert_fail_base.cold () from /usr/lib/libc.so.6
#4  0x00007ffff7dcd116 in __assert_fail () from /usr/lib/libc.so.6
#5  0x0000555555555170 in main (argc=1, argv=0x7fffffffe8c8) at main.c:4
(gdb) frame 5
#5  0x0000555555555170 in main (argc=1, argv=0x7fffffffe8d8) at main.c:4
4        assert(1==2);

If you instead make your own assert with builtin_trap(debugbreak on windows), you instead end up directly at the assert:

Program received signal SIGILL, Illegal instruction.
0x0000555555555175 in main (argc=1, argv=0x7fffffffe8d8) at main.c:11
11        ASSERT(1==2);

In order to get it to break at the assert we need to use a preprocessor macro. I try avoid using macros, but for debug stuff it can be nice.

#define ASSERT(expr)    \
{                       \
  if (expr) {           \
  } else {              \
    __builtin_trap();   \
  }                     \
}     

void main()
{
  ASSERT(1==2);
  return 0;
}

As long as the expression is true, nothing is executed, but if it’s false, we will have the execution stopped at the ASSERT() call. However, when you run that without a debugger it’ll just core dump, due to an Illegal instruction or Abort, instead of the regular Aborted (core dumped). Adding a fprintf with file and line number to the macro makes it similar to assert(). We can also support both posix and windows with an #ifdef:

#ifdef DEBUG
#ifdef _MSC_VER
#include <intrin.h>
#define debug_break() __debugbreak()
#else
#define debug_break() __builtin_trap()
#endif
#define ASSERT(expr)                        \
{                                           \
  if (expr) {                               \
  } else {                                  \
    fprintf(stderr, "ASSERT: '%s'@%s:%d\n", \
            #expr, __FILE__, __LINE__);     \
    debug_break();                          \
  }                                         \
}
#else
#define ASSERT(expr) // Does nothing in non-debug builds
#endif

In order to fully support windows, you may have to do some other further setup, prior to calling ASSERT(), in order to get output to console in Win32 applications.

You can also make an ASSERT(expr, msg) macro, but most of the time I don’t bother and just do ASSERT(1==2 && “One is not equal to two”), if I want to attach a message.