How to check if a parameter is an integral constan

2019-01-25 15:22发布

问题:

I'm currently cleaning up an existing C-library to publish it shamelessly.

A preprocessor macro NPOT is used to calculate the next greater power of two for a given integral constant expression at compile time. The macro is normally used in direct initialisations. For all other cases (e.g. using variable parameters), there is an inline function with the same function.

But if the user passes a variable, the algorithm expands to a huge piece of machine code. My question is: What may I do to prevent a user from passing anything but an integral constant expression to my macro?

#define NPOT(x)   complex_algorithm(x)

const int c=10;
int main(void) {
    int i=5;

    foo = NPOT(5);   // works, and does everything it should
    foo = NPOT(c);   // works also, but blows up the code extremely
    foo = NPOT(i);   // blows up the code also
}

What I already tried:

  1. Define the macro to #define NPOT(x) complex_algorithm(x ## u). It still works and throws a - even if hardly helpful - compiler error for variable parameters. Unless there is no variable like iu... Dirty, dangerous, don't want it.
  2. Documentation, didn't work for most users.

回答1:

You can use any expression that needs a constant integral expression and that will then be optimized out.

#define NPOT(X)                                         \
 (1                                                     \
 ? complex_algorithm(X)                                 \
 : sizeof(struct { int needs_constant[1 ? 1 : (X)]; })  \
 )

eventually you should cast the result of the sizeof to the appropriate integer type, so the return expression is of a type that you'd expect.

I am using an untagged struct here to

  • have a type so really no temporary is produced
  • have a unique type such that the expression can be repeated anywhere in the code without causing conflicts
  • trigger the use of a VLA, which is not allowed inside a struct as of C99:

A member of a structure or union may have any object type other than a variably modified type.

I am using the ternary ?: with 1 as the selecting expression to ensure that the : is always evaluated for its type, but never evaluated as an expression.

Edit: It seems that gcc accepts VLA inside struct as an extension and doesn't even warn about it, even when I explicitly say -std=c99. This is really a bad idea of them.

For such a weird compiler :) you could use sizeof((int[X]){ 0 }), instead. This is "as forbidden" as the above version, but additionally even gcc complains about it.



回答2:

#define INTEGRAL_CONST_EXPR(x) ((void) sizeof (struct {int a:(x);}), (x))

This will give a compile error if x is not a integral constant expression.

my_function(INTEGRAL_CONST_EXPR(1 + 2 + 3));    // OK
my_function(INTEGRAL_CONST_EXPR(1.0 + 2 + 3));  // compile error

Note that this solution does not work for initializing a static variable:

static int a = INTEGRAL_CONST_EXPR(2 + 3);

will trigger a compile error because of an expression with , is not a constant expression.

As @JensGustedt put in the comment, an integral constant expression resolving to a negative integer number cannot be used in this solution as bit-field width cannot be negative.