C Macro with _Pragma fails to compile

204 Views Asked by At

I'm trying to define a macro to return the result of a range check which works on signed and unsigned types. However, because I am compiling with -Wextra which includes -Wtype-limits, I want to ignore -Wtype-limits just for this macro. Unfortunately, this:

#define IN_RANGE(v, min, max) \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wtype-limits\"") \
    ( (((v) < (max)) && ((v) > (min))) ? true : false ) \
_Pragma("GCC diagnostic pop")

Fails to compile (because I'm also using -Werror) with:

... error: expected expression before '#pragma'
[build]    40 | _Pragma("GCC diagnostic push") \
[build]       | ^~~~~~~

Edit: here's a complete example.

#include <stdio.h>
#include <stdint.h>

#define IN_RANGE(v, min, max) \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wtype-limits\"") \
    ( (((v) < (max)) && ((v) > (min))) ? true : false ) \
_Pragma("GCC diagnostic pop")

const uint32_t MIN_NUM = 0;
const uint32_t MAX_NUM = 10;

int main() {
  printf("%s", IN_RANGE(8, MIN_NUM, MAX_NUM) ? "OK": "FAIL");
  return 0;
}

Fails with (on gcc 9):

$ gcc -Wall -Wextra example.c
example.c: In function ‘main’:
example.c:14:1: error: expected expression before ‘#pragma’
   14 |   printf("%s", IN_RANGE(8, MIN_NUM, MAX_NUM) ? "OK": "FAIL");
      | ^ ~
1

There are 1 best solutions below

0
On BEST ANSWER

Given some experimentation, it seems that you can't just embed the _Pragma() operator in the middle of an expression. In effect, it needs to be used where a statement can be used.

This code compiles, and AFAICT it is because there's a complete statement sandwiched between the _Pragma() operators:

#include <stdbool.h>
#include <stdio.h>

#define IN_RANGE2(r, v, min, max) \
    _Pragma("GCC diagnostic push") \
    _Pragma("GCC diagnostic ignored \"-Wtype-limits\"") \
        ((r) = (((v) < (max)) && ((v) > (min)))); \
    _Pragma("GCC diagnostic pop")

int main(void)
{
    int x = 10;
    bool y;
    IN_RANGE2(y, x, -9, +9);

    printf("%d\n", y);

    return 0;
}

Note the semicolon before the third _Pragma() operator. Without that, I was getting expected ‘;’ before ‘#pragma’ (as an error since I too compile with -Werror).

I've also simplified the conditional so it doesn't use the ternary operator, as I noted in a comment.

The relevant section of the standard is §6.10.9 Pragma operator. It says:

A unary operator expression of the form:

_Pragma ( string-literal )

is processed as follows: The string literal is destringized by deleting any encoding prefix, deleting the leading and trailing double-quotes, replacing each escape sequence \" by a double-quote, and replacing each escape sequence \\ by a single backslash. The resulting sequence of characters is processed through translation phase 3 to produce preprocessing tokens that are executed as if they were the pp-tokens in a pragma directive. The original four preprocessing tokens in the unary operator expression are removed.

Think about what the code expands to in your example:

int main(void)
{
  printf("%s", IN_RANGE(8, MIN_NUM, MAX_NUM) ? "OK": "FAIL");
  return 0;
}

is approximately equivalent to:

/* SO 7548-0114 */
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>

const uint32_t MIN_NUM = 0;
const uint32_t MAX_NUM = 10;

int main(void)
{
    printf("%s",
    #pragma GCC diagnostic push
    #pragma GCC diagnostic ignored "-Wtype-limits"
    ((((8) < (MAX_NUM)) || (8) > (MIN_NUM)) ? true : false)
    #pragma GCC diagnostic push
    ? "OK": "FAIL");
    return 0;
}

This doesn't compile. It produces the error:

pragma67.c: In function ‘main’:
pragma67.c:12:11: error: expected expression before ‘#pragma’
   12 |   #pragma GCC diagnostic push
      |           ^~~

Line 12 is the first #pragma directive.

I'm not totally convinced that this is what the standard mandates, but it is what I get from GCC 11.2.0.

However, when I compile with Apple's Clang (Apple clang version 14.0.0 (clang-1400.0.29.202)), both versions of the code I show compile OK. Thus, there is a discrepancy in the interpretation of the standard between these two major C compilers.

And, indeed, when I compile with Clang, your original code compiles cleanly. You can probably make a case out for a bug report to the GCC team. However, if you're going to write portable code, you'll need to accept the limitations imposed by GCC for a few years yet (though that depends on your portability requirements).