Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C compiler asserts: how to dynamically use them wherever the expression is fixed?

My code makes extensive use of compiler asserts like this to flag errors at build time in preference to run time, and to improve performance by not executing asserts at run time.

#define COMPILER_ASSERT(EXPR)    switch (0) {case 0: case (EXPR):;}

All good. I'd like to extend this to use compiler asserts for the following case. Say I have a macro which is called from 100 places, 99 of which pass a fixed value, 1 of which passes a variable. How can I code the macro to make this a compiler assert in 99 places and a runtime assert in the last one.

If I could guarantee that MY_FUNCTION() was always called with a fixed value I could code it like this.

void my_function(int x)
{
  //do something
}

#define MY_FUNCTION(X) \
  COMPILER_ASSERT(X != 0); \
  my_function(X)

//These can all take advantage of a compiler assert.
MY_FUNCTION(1);
MY_FUNCTION(SOME_HASH_DEFINE);
MY_FUNCTION(sizeof(SOME_STRUCTURE));
//This can't (this is a contrived example - actual code is complex).
int some_variable = 1;
MY_FUNCTION(some_variable);

So, if I can't guarantee that X is fixed, but want to take advantage for each call to MY_FUNCTION() where it is, how do I code it? Something like:

#define MY_FUNCTION(X) \
  if (X is a fixed value) COMPILER_ASSERT(X != 0); \
  else assert(X != 0); \ 
  my_function(X)

Recoding the calls to MY_FUNCTION() to only pass fixed values is not an option for me. Yes I could define MY_FUNCTION_FIXED_X and MY_FUNCTION_VARIABLE_X but that exposes all this to the calling code.

Thanks for your help. NickB

like image 266
NickB Avatar asked Nov 04 '22 08:11

NickB


1 Answers

If your C compiler supports variable-length arrays, you can write something like:

#define GENERIC_ASSERT(EXPR) \
  ((EXPR) ? (void) 0 : assert((EXPR)), (void) sizeof(char[(EXPR) ? 1 : -1]))

If EXPR is a false-valued compile time constant, this reduces to:

(assert((EXPR)), (void) sizeof(char[-1]))

which is a compile error (it involves a negative-length array).

If EXPR is a true-valued compile time constant, we get:

((void) 0), (void) 1)

Both Clang and gcc are capable of reducing the assert to nothing if invoked with a true-valued compile time constant.

If EXPR has a runtime value, the sizeof expression if invoked would result in a runtime error (e.g. an abort), so the assert is sequenced first through the use of the comma operator.

Unfortunately in the compile-time constant case, the error message output by gcc is not particularly illuminating:

prog.c:5: error: size of array ‘type name’ is negative

In Clang it's a bit better:

error: array size is negative
  GENERIC_ASSERT(2 + 2 == 5);
  ^~~~~~~~~~~~~~~~~~~~~~~~~~
note: expanded from:
  ((EXPR) ? (void) 0 : assert((EXPR)), (void) sizeof(char[(EXPR) ? 1 : -1]))
                                                          ^~~~~~~~~~~~~~~
like image 59
ecatmur Avatar answered Nov 09 '22 06:11

ecatmur