C-检测宏中的整数常数表达式



Linux内核邮件列表中关于一个宏的讨论,该宏测试其参数是否是整数常数表达式,并且是整数常数表达式本身。

Martin Uecker提出的一种不使用内置的特别聪明的方法(从Glibc的TGMATH.H中汲取灵感(是:

#define ICE_P(x) (sizeof(int) == sizeof(*(1 ? ((void*)((x) * 0l)) : (int*)1)))

如果参数是整数常数表达式,则该宏将扩展到值1的整数常数表达式,否则 0。但是,它依靠允许sizeof(void)(与sizeof(int)不同(,这是GNU C扩展。

是否可以在没有内置的情况下编写这样的宏,也不依赖语言扩展?如果是,它会评估其论点吗?


对于上面显示的宏的说明,请参见:Linux内核的__is_constexpr acro

使用相同的想法, ?:表达式的类型取决于参数是null指针常数还是普通void *,但使用_Generic检测到类型:

#define ICE_P(x) _Generic((1? (void *) ((x)*0) : (int *) 0), int*: 1, void*: 0)

iDeone上的演示。_Generic是C11的添加,因此,如果您陷入了C99或更早的东西,则无法使用它。

还具有标准链接,用于定义空指针常数以及Null指针常数与?:表达式类型相互作用的方式:

具有值0的整数常数表达式,或类型为void *的表达式称为空指针常数。

如果第二和第三操作数是指示器,或者一个是一个空指针常数,而另一个是指指针,则结果类型是指向符合所有操作数所引用类型的所有类型预选赛的类型。此外,如果两个操作数是兼容类型或兼容类型的不同合格版本的指示器,则结果类型是指定合成类型的适当合格版本的指针;如果一个操作数是一个空指针常数,则结果具有另一个操作数的类型;否则,一个操作数是一个指向空隙或合格版本的指针,在这种情况下,结果类型是指向适当合格版本的void

的指针。

我没有sizeof(void)的修复程序,但您可以通过执行以下操作来解决sizeof(void) == sizeof(int)的可能性:

#define ICE_P(x) ( 
  sizeof(void) != 
  sizeof(*( 
    1 ? 
      ((void*) ((x) * 0L) ) : 
      ((struct { char v[sizeof(void) * 2]; } *) 1) 
    ) 
  ) 
)

我知道这不是一个完整的答案,但是略微

edit :我已经对哪些解决方案在各种编译器上使用的解决方案进行了一些研究。我已经在赫德利编码了以下所有信息;请参阅HEDLEY_IS_CONSTANTHEDLEY_REQUIRE_CONTEXPRHEDLEY__IS_CONSTEXPR宏。它是公共领域和一个单个标头,因此很容易进入您的项目,或者您可以复制您感兴趣的位。

C11宏&变体

user2357112的C11宏应该在上使用任何 C11编译器,但是SUNCC和PGI当前被损坏,因此您必须将其列入列表。另外,IAR在C 模式下定义了__STDC_VERSION__,并且此技巧在C 中不起作用(Afaik nothing nothing(,因此您可能需要确保未定义__cplusplus。我已经证实了它确实可以在GCC,Clang(以及Emscripten等Clang衍生的编译器(,ICC,IAR和XL C/C 中使用。

除此之外,某些编译器即使在较旧的模式中也支持_Generic

  • GCC 4.9
  • clang;与__has_feature(c_generic_selections)核对(不过,您可能需要禁用-Wc11-extensions警告(
  • ICC 16.0
  • XL C/C 12.1

另外,请注意,当您将int施加到void*时,有时会发出警告;您可以首先施放到intptr_t 然后void*

来解决这个问题。
#define ICE_P(expr) _Generic((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0)

或,对于定义__INTPTR_TYPE__的编译器(例如GCC(,您可以使用它而不是intptr_t,并且不需要包含stdint.h

这里的另一个可能实现是使用__builtin_types_compatible_p而不是_Generic。我不知道有任何可行的编译器,但原始宏不会,但它确实使您摆脱了-Wpointer-arith警告:

#define IS_CONSTEXPR(expr) 
  __builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*)

此版本应与GCC一起工作,回到3.1,以及将__GNUC__/__GNUC_MINOR__定义为指示≥3.1的值的编译器,例如Clang和ICC。

宏来自此答案

任何支持sizeof(void)的编译器都可以工作,但是您很有可能会遇到警告(例如-Wpointer-arith(。也就是说, do 支持 sizeof(void)的afaict编译器似乎总是这样做的,因此这些编译器的任何版本都应该有效:

  • GCC
  • clang(以及内置的编译器,也定义__clang__(
  • ICC(测试18.0(
  • XL C/C (测试13.1.6(
  • ti(测试8.0(
  • tinycc

__builtin_constant_p

根据您的用例,最好在支持它的编译器上使用__builtin_constant_p。与整数常数表达相比,它更一般(和模糊(。它只是说编译器知道编译时的价值。这些编译器已知可以支持它:

  • GCC 3.1
  • clang
  • ICC(测试18.0(
  • tinycc 0.9.19
  • ARMCC 5.04
  • xl c/c (未记录,但它肯定可在13.1.6 (
  • 中起作用

如果您使用宏来选择代码路径,如果编译器在编译时知道值,但在运行时速度很慢,而代码路径则是编译器的黑匣子,但很快在运行时,使用__builtin_constant_p

OTOH,如果您想检查以确保该值确实是根据标准的冰,请不要使用__builtin_constant_p。例如,这是一个宏,如果expr是冰,它将返回expr,但是-1如果不是:

#if defined(ICE_P)
#  define REQUIRE_ICE(expr) (ICE_P(expr) ? (expr) : (-1))
#else
#  define REQUIRE_ICE(expr) (expr)
#endif

如果您使用VLA,则可以在宏中声明数组时使用该数组时使用该数组。

char foo[REQUIRE_ICE(bar)];

也就是说,GCC和Clang都实现了-Wvla警告,您可能需要使用该警告。-Wvla的优点是它不需要源代码修改(即,您只能编写char foo[bar];(。缺点是它并没有得到广泛的支持,并且使用一致的数组参数也将触发诊断,因此,如果您想避免大量的误报,则此宏可能是您最好的选择。

不支持任何内容的编译器

  • MSVC
  • DMC

欢迎想法:(

最新更新