_Static_assert 替换以在 C 中显示值

_Static_assert replacement to show value in C

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

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

Display integer at compile time in static_assert()

让 GCC 做到这一点出奇地困难。我找到了这个答案: 这表明是这样的:

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

等等

对于仅显示普通整数常量,一个简单的字符串化宏就可以做到:

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

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

static_size_assert(foo_t, 8) 将编译干净并且 return 正确。只要传递的数字是编译时整数常量而不是变量,它就可以工作。

根据 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,它会显示。

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

以上零案例按预期工作。