< home

lassert

iso c/c++ compatible static and runtime assertion for gcc 4.4.x
last updated: 29052010



introduction:

the proposed methods in this article are specifically aimed at the gnu gcc compiler (and its newer versions 4.4.0+). you may find these non-portable for other compilers. the methods will try not to add anything new, but allow fast code execution and minimal size overhead.

the code for the proposed methods is written in ansi c, yet it uses gcc function attributes which reduce its portability.
you may find other files named lassert.c or lassert.h searching the internet and such may also do a similar job which is assertion for c or c++ (at least runtime).

our goal is to compile with the following compiler flags for both gcc and g++ command lines:
-W -Wall -Wextra -ansi -pedantic -O3

the '-W -Wall -Wextra' flags will tell the compiler to enable pretty much all warnings (differs between versions), while '-ansi -pedantic' will generate errors and warnings which are relevant to the c / c++ iso standards (and ansi c). we use the '-O3' optimization level to ensure that our routines are working for highly optimized code.


runtime assertion:


runtime assertion in c and c++ is done with the assert() function (included in assert.h). the function from an assembler point of view does not produce much size overhead or reduce the performance that much, but still for localization of the method we can try writing our own. the block diagram for the common assert algorithm is:


                                 --> [yes] --> write to stdout (or stderr) --> terminate process
(runtime)                      /
expression -> check if zero ->
                               \
                                 --> [no] --> return 1, continue process


there aren't many ways we can do that, so here is the proposal:

--------------------------------------------------------------------------------

/* define a function that should not be inlined */
unsigned int _lassert() __attribute__(( noinline ));
/* initialize the function, use constants for passed variables */
unsigned int _lassert(const unsigned int e, const char* file,
  const unsigned int line, const char* e_str)
{
  /* if the expression results in zero */
  if (!e)
  {
    /* call builtin stdlib functions printf() and exit() */
    __builtin_printf("### lassert: %s, %d, (%s)\n", file, line, e_str);
    __builtin_exit(0);
  }
  return 1;
}
/* define a macro to pass the file, line and expression of the call */
#define lassert(e) _lassert((e), __FILE__, __LINE__, #e)

--------------------------------------------------------------------------------

note: the above lassert() method uses builtin stdlib functions which are defined in the assembly as _exit and _printf. if you have the '--fno-builtin' flag on you should include the appropriate headers and call the function normally (e.g. printf() defined in "stdio.h").

so why disable the inline and how much the above will be optimized with '-O3'? actually there aren't much benefits here as with '-O3' we are possibly going to gain a 'test eax, eax' instead of a 'cmp' and then something like a 'je' call instead of 'jne'. but we need to tell the compiler _not_ to inline the function, because we don't want the function to appear multiple times in the program (it is quite small so it is a candidate for inlining). instead we just want it to be allocated in an address in memory and be called to provide assertion. this will prevent any size overhead while the code will still execute fast.

the result from an assertion should look like:
<stdout>
### lassert: file, line, (expression)
<program terminates>


static assertion:

the static_assert() method is about to be integrated in c++0x, but until then we can try finding ways to implement static assertion that does not generate warnings and works for both c and c++ code. you may find that some of the well known methods such as divide by zero, does not work for g++ as iso c++ allows devision by zero. other methods such as attempting to initialize an array with negative size may produce errors such as 'ISO C90 forbids variable-size array', 'storage size of 'variable_name' isn't constant
' or other errors or warnings.

what we want from our compile time assertion is no warnings and errors.
here is the common algorithm:

                                 --> [yes] --> return error, abort compilation
(compile time)                 /
expression -> check if zero ->
                               \
                                 --> [no] --> return 1, continue compilation


of course we would also want to return the file and line where custom_static_assert() is called.

the proposal:
--------------------------------------------------------------------------------

/* gcc 4.4.0+ is required */
#if (__GNUC__ >= 4) && (__GNUC_MINOR__ >= 4)
  /* __lsa_true is a helper function that returns 1 */
  _AXSA_INLINE unsigned int __lsa_true (void) { return 1; }
  unsigned int __lsa (void)
    __attribute__ (( error("### lstatic_assert") ))
    __attribute__ (( optimize(0) ));
  /* define a macro to check the expression */
  #define lstatic_assert(e) ( (!(e)) ? __lsa(): __lsa_true() )
#else
  /* if no gcc 4.4.0+, define lstatic_assert() to return 0 */
  #define lstatic_assert(e) switch(0) { case 0: case (e): ; }
#endif

#else /* NDEBUG */
 
  _AXSA_INLINE unsigned int __lsa (void) { return 1; }
  #define lassert(e) __lsa()
  #define lstatic_assert(e) __lsa()

--------------------------------------------------------------------------------

if the compiler has the requested version or better we can use the static assertion method.
it is a very simple routine that uses a macro and a helper function. the macro is where we do the evaluation of the expression. if the result is other than 0 we return 1. if not we call the defined with the 'error(message)' attribute helper function __lsa, which will generate a compile time error. unfortunately this attribute is quite new to gcc but essential for this method to work.

if we take a look at the assembly, we will see that __lsa is called but there is just a definition (.def ___lsa). it should normally be initialized or it will produce a linker error. this isn't needed as the 'error' attribute generates the compile time error just before the code compilation is completed.

but why the 'optimize(0)' attribute?
since lstatic_assert(e) is a macro the code will most likely be always optimized. we need to make sure that the helper function is always present and called if the expression equals zero.

if gcc 4.4.0+ is not used, then we can do something like:

--------------------------------------------------------------------------------

#define lstatic_assert(e) switch(0) { case 0: case (e): ; }

--------------------------------------------------------------------------------

as an alternative method, which is very portable but has some problems, because the macro expands to a switch essentially.

we can also disable the lstatic_assert() or make it return 0 or 1 from a ?(1:0) condition, however both these methods may result in warnings such as:
'warning: [number] operand of conditional has no effect'
'warning: statement with no effect'

that's why we define a helper function __lsa to always return 0 instead. this should clear possible warnings, produce a small size overhead but reduce the speed a bit. alternatively we can also check if C99 is used and inline the function.
the second helper function __lsa_help returns 1. this again is used to prevent a 'warning: statement with no effect'.

result:
<file> : <line> : error: call to '__lsa' declared with attribute error: ### lstatic_assert
we do not see the expression, but we see the (### lstatic_assert) message and the <file> and <line>

note: #warning may produce a warning on its own ('#warning is a gcc extension')
with gcc / c99 you can use "#pragma message somestring" instead.

suggestions:

please, do not hesitate to send me any suggestions which can improve the proposed methods or this article.


download:

lassert.c


contact:


lubomir i. ivanov
neolit123 [at] gmail