_Static_assert replacement to show value in C

落花浮王杯 提交于 2019-12-11 17:08:36

问题


Is it possible to have the compiler error/warning diagnostic output a compile-time computed numeric value in C11 or C17 (i.e. not using templates)? The below link does this in C++ using template magic. The intention is to use this as a _Static_assert replacement which prints the values of the non-equal failed expression. Ideally, it should be able to evaluate the expression as true or false, and print only when it fails evaluation.

This is obviously compiler-dependent, so assume GCC.

Display integer at compile time in static_assert()


回答1:


This is surprisingly difficult to get GCC to do. I found this answer: https://stackoverflow.com/a/35261673/502399 which suggests something like this:

void error() {
    int array[sizeof(struct X)];
    __builtin_printf("%d", &array);
}

which outputs something like

foo.c: In function ‘error’:
foo.c:8:21: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘int (*)[8]’ [-Wformat=]                      

  __builtin_printf("%d", &array);
                    ~^   ~~~~~~

as long as you can pass -Wformat or -Wall or something.

To see if there's an easier way, I grepped the GCC source for that message and found that the argument type was printed with a special GCC-specific %qT format string, so I looked for other uses of that string. Specifically I was looking for its use in errors, rather than warnings, so that it would work regardless of warning flags. I found a use in binary_op_error(), from which I made this example:

int array[sizeof(struct X)];
int error = 1 / &array;

which produces

foo.c:7:15: error: invalid operands to binary / (have ‘int’ and ‘int (*)[8]’)
 int error = 1 / &array;
               ^ ~~~~~~

other possibilities include

int array[sizeof(struct X)];
int error = __sync_fetch_and_add(&array, 1);

and

int error = _Generic((int (*)[sizeof(struct X)])0, int: 0);

and

int foo(double bar);
int error = foo((int (*)[sizeof(struct X)])0);

etc.




回答2:


For just displaying normal integer constants, a simple stringification macro will do:

#define STRINGIFY(x) #x
#define STR(x) STRINGIFY(x)
...
STR(123)

For the sizeof operator specifically, it gets trickier as that one is evaluated later than when macro expansion takes place in the pre-processor. Since C11 is available, you could perhaps use _Generic instead.

You can create a temporary compound literal with the size of the struct, then have _Generic compare a pointer to the created type with a pointer to another array of the expected size.

For example we can create the compound literal (char[sizeof(type)]){0} where the type char doesn't really matter, then take the address of that, &(char[sizeof(type)]){0}. Compare this type with an array pointer to an array of the expected size:

_Generic( &(char[sizeof(type)]){0}, 
          char(*)[expected] : true )

Full example:

#include <stdbool.h>

#define static_size_assert(type, expected) \
  _Generic( &(char[sizeof(type)]){0}, char(*)[expected] : true)


int main (void)
{
  typedef struct { char s[3]; int i; } foo_t;

  if(static_size_assert(foo_t, 7))
  {
    // ...
  }
  return 0;
}

In case of the expected struct padding, this will cause a standard-compliant compiler to produce an error message like (from gcc):

error: '_Generic' selector of type 'char (*)[8]' is not compatible with any association

whereas static_size_assert(foo_t, 8) will compile cleanly and return true. It works as long as the number passed is a compile-time integer constant and not a variable.




回答3:


Building off the answers of Lundin and Tavian, the generic solution macro for positive and negative values is as follows:

#define STATIC_ASSERT_EQUAL(VALUE, EXPECTED)                                   \
  (void)_Generic(&(char[(EXPECTED) > 0 ? (VALUE) : (EXPECTED) < 0 ?            \
                        -(VALUE) : (VALUE) == 0 ? 0x7FFFFFFF : (VALUE)]){0},   \
                 char(*)[(EXPECTED) > 0 ? (EXPECTED) : (EXPECTED) < 0 ?        \
                         -(EXPECTED) : 0x7FFFFFFF] : 0)

The macro must be used inside a function. The idea is that EXPECTED is a value known by the programmer, and VALUE is the unknown calculated value. This was tested with GCC 6.1.

These pass without error:

STATIC_ASSERT_EQUAL(-1, -1);
STATIC_ASSERT_EQUAL(0, 0);
STATIC_ASSERT_EQUAL(1, 1);

However, this also unfortunately passes without error, because 0 aliases with 0x7FFFFFFF:

STATIC_ASSERT_EQUAL(0x7FFFFFFF, 0);

The error case appears as:

STATIC_ASSERT_EQUAL(2, 1);
error: ‘_Generic’ selector of type ‘char (*)[2]’ is not compatible with any association

If you have certain warnings enabled:

STATIC_ASSERT_EQUAL(0, 1);
error: ISO C forbids zero-size array [-Werror=pedantic]

Since EXPECTED is not 0, then the programmer can assume VALUE == 0.

STATIC_ASSERT_EQUAL(-2, 1);
error: size of unnamed array is negative

The value is not displayed in this case, but the programmer can see the VALUE is negative when it shouldn't be, so negate VALUE, and it will display.

STATIC_ASSERT_EQUAL(2, -1);
error: size of unnamed array is negative

Similarly for above, the programmer knows the VALUE should be negative, but isn't, so negating VALUE will make it display.

STATIC_ASSERT_EQUAL(-2, -1);
error: ‘_Generic’ selector of type ‘char (*)[2]’ is not compatible with any association

The programmer knows it should be negative, and here it displays the positive equivalent of VALUE.

STATIC_ASSERT_EQUAL(2, 0);
error: ‘_Generic’ selector of type ‘char (*)[2]’ is not compatible with any association

The above zero case works as expected.



来源:https://stackoverflow.com/questions/53310844/static-assert-replacement-to-show-value-in-c

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!