Detecting Integer Constant Expressions in Macros

时间:2018-03-25 20:09:31

标签: c linux-kernel macros language-lawyer

There was a discussion in the Linux kernel mailing list regarding a macro that tests whether its argument is an integer constant expression and is an integer constant expression itself.

One particularly clever approach that does not use builtins, proposed by Martin Uecker (taking inspiration from glibc's tgmath.h), is:

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

This macro expands into an integer constant expression of value 1 if the argument is an integer constant expression, 0 otherwise. However, it relies on sizeof(void) to be allowed (and different than sizeof(int)), which is a GNU C extension.

Is it possible to write such a macro without builtins and without relying on language extensions? If yes, does it evaluate its argument?


For an explanation of the macro shown above, see instead: Linux Kernel's __is_constexpr Macro

2 个答案:

答案 0 :(得分:25)

Use the same idea, where the type of a ?: expression depends on whether an argument is a null pointer constant or an ordinary void *, but detect the type with _Generic:

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

Demo on Ideone. _Generic is a C11 addition, so if you're stuck on C99 or something earlier, you won't be able to use it.

Also, have standard links for the definition of a null pointer constant and the way null pointer constants interact with the type of a ?: expression:

An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant.

and

If both the second and third operands are pointers or one is a null pointer constant and the other is a pointer, the result type is a pointer to a type qualified with all the type qualifiers of the types referenced by both operands. Furthermore, if both operands are pointers to compatible types or to differently qualified versions of compatible types, the result type is a pointer to an appropriately qualified version of the composite type; if one operand is a null pointer constant, the result has the type of the other operand; otherwise, one operand is a pointer to void or a qualified version of void, in which case the result type is a pointer to an appropriately qualified version of void.

答案 1 :(得分:18)

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

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

我知道这不是一个完整的答案,但是稍微更接近......

编辑:我已经对哪些解决方案适用于各种编译器进行了一些研究。我在Hedley中编码了以下所有信息;请参阅HEDLEY_IS_CONSTANTHEDLEY_REQUIRE_CONTEXPRHEDLEY__IS_CONSTEXPR宏。它是公共领域和单个标题,所以只需放入您的项目就很容易,或者您可以复制您感兴趣的位。

C11 Macro&变体

user2357112的C11宏任何 C11编译器上运行,但SunCCPGI目前已被破坏,因此您必须将它们列入黑名单。此外,IAR在C ++模式下定义__STDC_VERSION__,并且此技巧在C ++(AFAIK nothing does)中不起作用,因此您可能希望确保未定义__cplusplus。我已经证实它确实适用于GCC,clang(以及类似emscripten的clang派生编译器),ICC,IAR和XL C / C ++。

除此之外,一些编译器支持_Generic,即使在较旧的模式下也是如此:

  • GCC 4.9+
  • 铛;请查看__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)。也就是说,支持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 +
  • ICC(测试18.0)
  • TinyCC 0.9.19 +
  • armcc 5.04 +
  • XL C / C ++(未记载,但绝对适用于13.1.6 +)

如果您正在使用宏来选择编译器可以常量折叠的代码路径,如果它在编译时知道该值但在运行时很慢并且代码路径是编译器的黑盒子但是很快在运行时,使用__builtin_constant_p

OTOH,如果您想根据标准检查以确保该值确实是ICE,请不要使用__builtin_constant_p。例如,如果expr是ICE,则返回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];)。缺点是它没有得到广泛的支持,并且使用conformant array parameters也会触发诊断,所以如果你想避免很多误报,这个宏可能是你最好的选择。

不支持任何内容的编译器

  • MSVC
  • DMC

欢迎提示:)