4

I'm trying to build a simple SIGNOF macro:

#define SIGNOF(a) ((a) < 0 ? -1 : 1)

If a is negative it should return -1, otherwise 1. If a is an unsigned type, it should always return 1 and the compiler can optimize away the negative code path.

However, GCC rightfully warns me that

error: comparison of unsigned expression in ‘< 0’ is always false [-Werror=type-limits]
   29 | #define SIGNOF(a) ((a) < 0 ? -1 : 1)

But in this case I actually want this behavior. Is there any way to tell the compiler that this is intentional, similar to /* fall-though */ in a switch-case?

user1273684
  • 1,559
  • 15
  • 24
  • 3
    Why not avoid using the macro when the type is unsigned? When don't you know the type of your variable? – Jonathan Leffler Feb 24 '22 at 15:43
  • I want to use this macro inside of another macro: `#define DIV_ROUND(a, b) ((a) + SIGNOF(a) * (b)/2) / (b)` – user1273684 Feb 24 '22 at 15:44
  • You are missing a set of parentheses around the expression as a whole in your proposed macro. – Jonathan Leffler Feb 24 '22 at 15:48
  • @Jonathan Leffler You could have a macro that expands to a signed or unsigned type. But that's a problematic practice! – ikegami Feb 24 '22 at 15:48
  • Make two versions of the macro? Or you can use generic selection macro. – Eugene Sh. Feb 24 '22 at 15:48
  • 1
    Does this answer your question? https://stackoverflow.com/a/935667/421195 See also: https://gcc.gnu.org/onlinedocs/gcc-11.2.0/gcc/Warning-Options.html#Warning-Options – paulsm4 Feb 24 '22 at 15:52
  • A negative result (but maybe it will inspire someone else): — I've tried playing with the `_Pragma` operator using `#define P0 _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wtype-limits\"")` and `#define P1 _Pragma("GCC diagnostic pop")` without being able to embed those in a macro. When I run `cpp source.c`, P0 expands to 6 lines with `#` controls and P1 expands into 3 lines, and I've not been able to embed them in another macro usefully. Using `P0` on one line, then `SIGNOF` on the next, and `P1` on the third line suppresses the warning or error. – Jonathan Leffler Feb 24 '22 at 16:44

4 Answers4

2

If your compiler supports it, you can use _Generic:

#define SIGNOF(a) _Generic(a, unsigned char: 1,          \
                              unsigned short: 1,         \
                              unsigned int: 1,           \
                              unsigned long: 1,          \
                              unsigned long long: 1,     \
                              default: (a) < 0 ? -1 : 1)
chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • Unfortunately this falls back to `default` for `UINT32_MAX` where it then complains about `comparison of unsigned expression in ‘< 0’ is always false` – user1273684 Feb 24 '22 at 16:01
  • @user1273684 Are you sure? https://ideone.com/PtE1VW - this shows that the type of UINT32_MAX is detected properly. – Eugene Sh. Feb 24 '22 at 16:10
  • Yes this still fails with `-Werror=type-limits` https://godbolt.org/z/oGYcjranx – user1273684 Feb 24 '22 at 16:14
  • This is weird. In https://godbolt.org/z/TWG45vser the warning clearly shows that `UINT32_MAX` is considered `unsigned int`... – Eugene Sh. Feb 24 '22 at 16:24
  • Looks like it is performing the type limits check *before* actually picking the right generic option. Here - it will produce the warning for two different (unrelated) lines: https://godbolt.org/z/1dv6T1333 – Eugene Sh. Feb 24 '22 at 16:30
  • That does *somewhat* make sense, since those are all expressions, even if they're not evaluated. (Not that I necessarily agree with the warning.) – Thomas Jager Feb 24 '22 at 16:35
  • 1
    Well, it is kind of making this answer incorrect (in a sense it does not answer the question...) – Eugene Sh. Feb 24 '22 at 16:36
  • @EugeneSh.: The link https://godbolt.org/z/oGYcjranx has a syntax error (extra `)` for the `unsigned short` selector), but once corrected, shows the problem with **gcc**: it complains about the 2 expansions `unsigned short` and `default` that have the constant expression, neither of which is selected for the type of `UINT32_MAX`, ie: `unsigned int`. This looks like a bug or at least a shortcoming, that does not occur with **clang** even with all warnings enabled. – chqrlie Feb 25 '22 at 06:46
2

What works is

static inline int __signof(long long a)
{
    return a < 0 ? -1 : 1;
}

#define SIGNOF(a) _Generic(a, unsigned char: 1,          \
                              unsigned short: 1,         \
                              unsigned int: 1,           \
                              unsigned long: 1,          \
                              unsigned long long: 1,     \
                              default: __signof(a))
user1273684
  • 1,559
  • 15
  • 24
1

This seems to fix the warning problem, at the expense of evaluating the operand twice:

#define SIGNOF(a) ((a) == 0 ? +1 : ((a) > 0) ? +1 : -1)

I observe that since the proposed DIV_ROUND() macro evaluates both its arguments twice, it also has problems if the arguments have side effects (increments, function calls, etc).

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
0

Time for a hideous workaround:

#define SIGNOF_(a) ((a) < 0 ? -1 : 1)

#ifdef __GNUC__
#define SIGNOF(a)   ({ \
        typeof(a) _a = (a); \
        _Pragma("GCC diagnostic push") \
        _Pragma("GCC diagnostic ignored \"-Wtype-limits\"") \
        int _r = SIGNOF_(_a); \
        _Pragma("GCC diagnostic pop") \
        _r; })
#else
#define SIGNOF(a)   SIGNOF_(a)
#endif

It makes use of GNU C "statement expressions" (({ statements; })) and the typeof operator. The initialization _a = (a); is to catch any -Wtype-limit warnings in the macro parameter a before the warning is temporarily disabled by the pragmas.

Ian Abbott
  • 15,083
  • 19
  • 33