cgccmacrosgcc12

C Macro with _Pragma fails to compile


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");
      | ^ ~

Solution

  • 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).