Saturday, August 24, 2013

Base Metal Blog Weekend Edition, August 24-25, 2013

Assert Yourself Conditionally


How many times have you written this code:

void Fn(TYPE *val)
{
    ASSERT(val);
    if(val)
    {
        //Do some stuff
    }
}


Or worse, this code:

void Fn(TYPE *val)
{
    ASSERT(val && "You are about to crash!");
    val->Fn();
}
Now there's a better way!  Introducing the if_assert:

void Fn(TYPE *val)
{
    if_assert(val) 
    {
        //Do some stuff
    }
}


Which is just defined as:

#ifdef DEBUG
     #define if_assert(x) if(!!(x) || System::AssertFail(#x " " STRINGIFY(__FILE__) ":" STRINGIFY(__LINE__)))
#else
    #define if_assert(x) if(x)
#endif


Some points on that code:
  • You'll need to provide your own bool System::AssertFail(const char *msg) function
    • It should return false.
    • My System::AssertFail(...) just prints the message passed to it and does a normal ASSERT(0)
    • I have this defined as extern rather than inline.  That keeps the contents of System::AssertFail(...) out of the instruction cache unless the assert actually fails, which depending on the complexity of your ASSERT handler can be substantial.
  • In non-DEBUG builds, this just reduces to if(x)
  • The !!(x) basically means the same thing as (x), but the compiler doesn't complain about automatic conversion to bool
  • Because C uses lazy evaluation, nothing after the || will get evaluated if (x) is true
  • STRINGIFY just converts its argument to a string.  There is some magic happening there, though, because you want the compiler to convert __FILE__ to a string before the macro converts it to the string (otherwise you just get "__FILE__" instead of the actual file name), so:
    • #define _STRINGIFY(x) #x
    • #define STRINGIFY(x) _STRINGIFY(x)
  • Unfortunately, this doesn't support declaring variables inside the if_assert:
    • Do:
      • TYPE *val = GetValue();
      • if_assert(val) ...
    • not:
      • if_assert(TYPE *val = GetValue()) ...
    • though if you're dong this, you probably don't need an ASSERT:
      • if(TYPE *val = GetValue()) ...

2 comments:

  1. Hey Craig!

    What about void Fn(TYPE &val)?

    In other words, make the caller do the check. To me, pointer means optional and if we're putting in an assert it's clearly not optional, which makes for a confusing interface.

    ReplyDelete
  2. Well, that's strictly example code of course. :)

    I've typically been doing this in cases where one component is expecting another component to be present on the same game object. There's a pointer that needs to be bound to the other component well after construction, so it can't be a reference.

    This basically lets me know "the data is bad" without crashing the game, so I can fix it and reload it without restarting the game.

    ReplyDelete