检测宏中的整数常量表达式

Aco*_*orn 35 c macros linux-kernel language-lawyer

Linux内核邮件列表中讨论了一个宏,该宏测试其参数是否为整数常量表达式,并且本身是一个整数常量表达式.

Martin Uecker提出的一种不使用内置函数的特别聪明的方法(从glibc的tgmath.h中获取灵感)是:

#define ICE_P(x) (sizeof(int) == sizeof(*(1 ? ((void*)((x) * 0l)) : (int*)1)))
Run Code Online (Sandbox Code Playgroud)

1如果参数是整数常量表达式,0则此宏扩展为值的整数常量表达式,否则.但是,它依赖于sizeof(void)允许(和不同于sizeof(int)),这是一个GNU C扩展.

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


有关上面显示的宏的解释,请参阅:Linux Kernel的__is_constexpr宏

use*_*ica 25

使用相同的想法,其中?:表达式的类型取决于参数是否为空指针常量或普通void *,但检测类型_Generic:

#define ICE_P(x) _Generic((1? (void *) ((x)*0) : (int *) 0), int*: 1, void*: 0)
Run Code Online (Sandbox Code Playgroud)

Ideone上的演示. _Generic是一个C11添加,所以如果你坚持使用C99或更早的东西,你将无法使用它.

此外,还有用于定义空指针常量的标准链接以及空指针常量与?:表达式类型交互的方式:

值为0的整型常量表达式或类型为void*的表达式称为空指针常量.

如果第二个和第三个操作数都是指针,或者一个是空指针常量而另一个是指针,则结果类型是指向使用两个操作数引用的类型的所有类型限定符限定的类型的指针.此外,如果两个操作数都是兼容类型的指针或兼容类型的不同限定版本,则结果类型是指向复合类型的适当限定版本的指针; 如果一个操作数是空指针常量,则结果具有另一个操作数的类型; 否则,一个操作数是指向void的指针或void的限定版本,在这种情况下,结果类型是指向适当限定版本的void的指针.

  • @Acorn:我还没有想出任何可以在C89中运行的东西.我能想到的一切都需要C++或编译器扩展 - 例如decltype,typeof,SFINAE,函数重载,模板特化等等.另外,`?:`技巧似乎只允许你在`void*之间做出选择'非常不方便`或其他指针类型; 如果我们可以让它在两个函数指针类型之间进行选择,我们可以测试`sizeof`返回值 - 或者我忽略的更简单的东西,如果我们可以在两个对象指针类型之间进行选择,我们可以只取消引用和`sizeof`. (2认同)

nem*_*equ 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) \
    ) \
  ) \
)
Run Code Online (Sandbox Code Playgroud)

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

编辑:我已经做了一些关于哪些解决方案适用于各种编译器的研究.我在Hedley编码了所有以下信息; 看HEDLEY_IS_CONSTANT,HEDLEY_REQUIRE_CONTEXPRHEDLEY__IS_CONSTEXPR宏.它是公共领域和单个标题,所以只需插入项目就很容易,或者你可以复制你感兴趣的位.

C11宏观和变形

user2357112的C11宏应该可以在任何 C11编译器上运行,但是SunCCPGI目前已经坏了,所以你必须将它们列入黑名单.此外,IAR __STDC_VERSION__在C++模式下定义,并且这个技巧在C++中不起作用(AFAIK 没有任何作用),因此您可能希望确保__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为a 时发出警告void*; 你可以通过先铸造一个intptr_t 然后来解决这个问题void*:

#define ICE_P(expr) _Generic((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0)
Run Code Online (Sandbox Code Playgroud)

或者,对于定义的编译器(例如GCC)__INTPTR_TYPE__,您可以使用它代替而不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*)
Run Code Online (Sandbox Code Playgroud)

此版本应与GCC一起使用3.1,以及定义__GNUC__/ __GNUC_MINOR__表示≥3.1的值的编译器,如clang和ICC.

这个答案的宏

任何支持的编译器都sizeof(void)应该工作,但很可能会遇到警告(例如-Wpointer-arith).这就是说,AFAICT的编译器支持sizeof(void)似乎一直这样做,所以任何这些编译器的版本应该工作:

  • 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如果它expr是一个ICE 将返回,但如果不是,则返回-1:

#if defined(ICE_P)
#  define REQUIRE_ICE(expr) (ICE_P(expr) ? (expr) : (-1))
#else
#  define REQUIRE_ICE(expr) (expr)
#endif
Run Code Online (Sandbox Code Playgroud)

然后,如果编译器在使用VLA时显示错误,则可以在宏中声明数组时使用它:

char foo[REQUIRE_ICE(bar)];
Run Code Online (Sandbox Code Playgroud)

也就是说,GCC和clang都实施了一个-Wvla警告,你可能想要使用它.优点-Wvla是它不需要修改源代码(即,你可以只写char foo[bar];).缺点是它没有得到广泛的支持,并且使用一致的数组参数也会触发诊断,所以如果你想避免大量的误报,这个宏可能是你最好的选择.

编译器不支持任何东西

  • MSVC
  • DMC

想法欢迎:)

  • 我想这不是真正的合作设计.尽管如此,恕我直言,它有点温和,所以我宁愿放弃它,即使它需要一些声誉. (3认同)