Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detecting Integer Constant Expressions in Macros

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

like image 460
Acorn Avatar asked Mar 25 '18 20:03

Acorn


2 Answers

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.

like image 80
user2357112 supports Monica Avatar answered Oct 22 '22 21:10

user2357112 supports Monica


I don't have a fix for sizeof(void) not being standard, but you could work around the possibility that sizeof(void) == sizeof(int) by doing something like:

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

I know it's not a full answer, but it's slightly closer…

Edit: I've done a bit of research about which solutions work on various compilers. I've encoded all of the following information in Hedley; see the HEDLEY_IS_CONSTANT, HEDLEY_REQUIRE_CONTEXPR, and HEDLEY__IS_CONSTEXPR macros. It's public domain, and a single header, so it's very easy to just drop into your project, or you can copy the bits you're interested in.

C11 Macro & Variants

user2357112's C11 macro should work on any C11 compiler, but SunCC and PGI are currently broken so you'll have to blacklist them. Also, IAR defines __STDC_VERSION__ in C++ mode, and this trick doesn't work in C++ (AFAIK nothing does), so you'll probably want to make sure __cplusplus isn't defined. I've verified that it really does work on GCC, clang (and clang-derived compilers like emscripten), ICC, IAR, and XL C/C++.

Other than that, some compilers support _Generic even in older modes as an extension:

  • GCC 4.9+
  • clang; check with __has_feature(c_generic_selections) (you may want to disable the -Wc11-extensions warning, though)
  • ICC 16.0+
  • XL C/C++ 12.1+

Also, note that sometimes compilers emit a warning when you cast an int to a void*; you can get around this by first casting to an intptr_t then to void*:

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

Or, for compilers (such as GCC) which define __INTPTR_TYPE__, you can use that instead of intptr_t and you needn't include stdint.h.

Another possible implementation here is to use __builtin_types_compatible_p instead of _Generic. I'm not aware of any compilers where that would work but the original macro wouldn't, but it does get you out of a -Wpointer-arith warning:

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

This version should work with GCC back to 3.1, as well as compilers which define __GNUC__/__GNUC_MINOR__ to values which indicate ≥ 3.1 such as clang and ICC.

Macro from this answer

Any compiler which supports sizeof(void) should work, but there is a good chance you'll run into a warning (such as -Wpointer-arith). That said, AFAICT compilers which do support sizeof(void) seem to have always done so, so any version of these compilers should work:

  • GCC
  • Clang (and compilers built in in, which also define __clang__)
  • ICC (tested 18.0)
  • XL C/C++ (tested 13.1.6)
  • TI (tested 8.0)
  • TinyCC

__builtin_constant_p

Depending on your use case, it may be preferable to use __builtin_constant_p on compilers which support it. It's a bit more general (and more nebulous) than an integer constant expression; it just says that the compiler knows the value at compile-time. These compilers are known to support it:

  • GCC 3.1+
  • Clang
  • ICC (tested 18.0)
  • TinyCC 0.9.19+
  • armcc 5.04+
  • XL C/C++ (undocumented, but it definitely works in 13.1.6+)

If you're using the macro to choose between a code path which the compiler can constant fold if it knows the value at compile time but is slow at runtime and a code path which is a black box to the compiler but is fast at runtime, use __builtin_constant_p.

OTOH, if you want to check to make sure the value is really an ICE according to the standard, don't use __builtin_constant_p. As an example, here is a macro which will return expr if the expr is an ICE, but -1 if it isn't:

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

You can then use that when declaring an array in a macro if you the compiler to show an error if you use a VLA:

char foo[REQUIRE_ICE(bar)];

That said, GCC and clang both implement a -Wvla warning which you may want to use instead. The advantage of -Wvla is that it doesn't require source code modifications (i.e., you can just write char foo[bar];). The disadvantages are that it isn't as widely supported, and that using conformant array parameters will also trigger the diagnostic, so if you want to avoid lots of false positives this macro may be your best bet.

Compilers which don't support anything

  • MSVC
  • DMC

Ideas welcome :)

like image 18
nemequ Avatar answered Oct 22 '22 22:10

nemequ