_Static_assert替换以在C中显示值

时间:2018-11-15 00:37:02

标签: c gcc compiler-errors c11 static-assert

能否在C11或C17中使编译器错误/警告诊断输出一个编译时计算出的数值(即不使用模板)?下面的链接使用模板魔术在C ++中执行此操作。目的是将其用作_Static_assert替换,此替换将打印不等价失败表达式的值。理想情况下,它应该能够将表达式评估为真或假,并且仅在评估失败时才打印。

这显然是依赖于编译器的,所以假设使用GCC。

Display integer at compile time in static_assert()

3 个答案:

答案 0 :(得分:1)

这令人惊讶地难以完成GCC。我找到了这个答案:https://stackoverflow.com/a/35261673/502399暗示着这样的东西:

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

输出类似

的内容
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);
                    ~^   ~~~~~~

,只要您可以传递-Wformat或-Wall或其他任何内容即可。

要查看是否有更简便的方法,我为该消息添加了GCC源,发现参数类型使用特殊的GCC特定%qT格式字符串打印,因此我寻找了该字符串的其他用途。特别是我在寻找它在错误而不是警告中的用法,以便无论警告标志如何都可以使用它。我在binary_op_error()中找到了一种用法,我以此为例:

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

产生

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

其他可能性包括

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

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

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

答案 1 :(得分:1)

仅显示普通整数常量,可以使用一个简单的字符串化宏:

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

特别是对于sizeof运算符,因为它比在预处理器中进行宏扩展时要晚进行求值而变得更加棘手。由于C11可用,因此您可以改用_Generic

您可以创建一个具有该结构大小的临时复合文字,然后让_Generic将指向创建的类型的指针与另一个预期大小的数组的指针进行比较。

例如,我们可以创建复合文字(char[sizeof(type)]){0},其中类型char并不重要,然后使用其地址&(char[sizeof(type)]){0}。将此类型与指向预期大小的数组的数组指针进行比较:

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

完整示例:

#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;
}

在预期的结构填充情况下,这将导致符合标准的编译器生成错误消息,例如(来自gcc):

  

错误:类型为'char(*)[8]'的'_Generic'选择器与任何关联都不兼容

static_size_assert(foo_t, 8)将干净地编译并返回true。只要传递的数字是编译时整数常量而不是变量,它就起作用。

答案 2 :(得分:0)

基于Lundin和Tavian的答案,正值和负值的通用解决方案宏如下:

#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)

必须在函数内部使用宏。这个想法是EXPECTED是程序员已知的值,而VALUE是未知的计算值。这已在GCC 6.1上进行了测试。

这些通过没有错误:

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

但是,不幸的是,这也没有错误地通过,因为0别名为0x7FFFFFFF:

STATIC_ASSERT_EQUAL(0x7FFFFFFF, 0);

错误情况显示为:

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

如果启用了某些警告:

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

由于EXPECTED不为0,因此程序员可以假定VALUE == 0。

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

在这种情况下不会显示该值,但是程序员应该在不应该看到VALUE的情况下看到VALUE,因此取反VALUE,它将显示。

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

与上述类似,程序员知道VALUE应该为负数,但不是,因此否定VALUE将使其显示。

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

程序员知道它应该是负数,在这里它显示VALUE的正数。

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

上述零情况可以正常工作。