C Macro with _Pragma fails to compile - c

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

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

Related

C macros, what's the meaning of ((void)0)?

Given the following code written according to the C99 standard:
#define LOW 1
#define MEDIUM 2
#define HIGH 3
#define LOGGING_LEVEL HIGH
#if LOGGING_LEVEL >= MEDIUM
#define LOG_MEDIUM(message) printf(message)
#else
#define LOG_MEDIUM(message) ((void)0)
#endif
void load_configuration() {
//...
LOG_MEDIUM("Configuration loaded\n");
}
what's the purpose of ((void)0) I searched the web a lot but nothing found regarding this.
Plus, why didn't we wrote ; after using printf(message)
The void-cast fixes a compiler warning. Here's an analogous testcase:
int main(void)
{
0; // generates "foo.c:3:2: warning: statement with no effect"
(void)0;
return 0;
}
and (using a script to add gcc's warning flags) you see a warning for the line without a cast:
$ gcc-stricter -c foo.c
foo.c: In function ‘main’:
foo.c:3:2: warning: statement with no effect [-Wunused-value]
0;
^
The extra parentheses and lack of semicolon allow the macro's result to be used interchangeably with the printf.
Main idea is to exclude all LOG_MEDIUM if the criteria was not meet.
After compilation those calls will not affect functionality.

Suppressing -Wunused-value with gcc

Considering this piece of code:
#define STC_ASSERT(X,Msg) \
(!!sizeof(struct{char STC_ASSERT;_Static_assert((X),Msg "");}))
#define A 43
#define B 42
#define C (STC_ASSERT(A-B>=0,"")?(A-B):0)
enum { c = C };
int main()
{
STC_ASSERT(1,""); //can this not generate a warning on gcc?
}
Is there anything I can do inside the STC_ASSERT _Static_assert wrapper to prevent the line in main from generating an -Wunused-value warning when compiling with gcc -Wall -Wextra?
(_Pragma with GCC diagnostic push/pop doesn't work here. For a macro like #define FOO 42, embracing it in an expression statement ( ({ 42; }) ) would do the trick but that would prevent the macro from being used in contexts where an Integer Constant Expression is required (such as the enum definition))

Delayed stringification without macro expansion

Is it possible to modify the following code fragment to prevent the diagnostic printed by #pragma GCC warning from changing if any of the identifier tokens in deprecation_message are defined as object-like macros at the point where dmacro is expanded, while preserving the ability to substitute symbol into the message? Ugliness no object, GCC extensions are fair game as long as clang also implements them, but the contents of the diagnostic may not be modified.
#define deprecation_message(symbol) \
#symbol will be removed from <header.h> in the next release\n\
of $LIBRARY. To use #symbol, include <moved/header.h> instead.
#define make_pw(...) make_pw_(__VA_ARGS__)
#define make_pw_(...) make_pw__(GCC warning #__VA_ARGS__)
#define make_pw__(...) _Pragma(#__VA_ARGS_)
#define dmacro(a,b,c) make_pw(deprecation_message(dmacro)) xmacro(a,b,c)
// Uncommenting any of the following #define lines should *not*
// change the text of the diagnostic in any way.
//#define header
//#define n f
//#define will won't'
dmacro(x,y,z)
(You might be tempted to reach for string-literal concatenation, but that won't work; both _Pragma itself and #pragma GCC warning accept only a single string literal. _Pragma("this" "that") is a syntax error.)
Maybe I misunderstood the question, but the following code is working and compiling on both clang and gcc, and tested with -std=c99 and -std=c11:
#include <stdio.h>
#include <stdlib.h>
#define deprecation_message(symbol) \
#symbol " will be removed from <header.h> in the next release\n" \
"of $LIBRARY. To use " #symbol ", include <moved/header.h> instead."
#define make_pw__(...) _Pragma(#__VA_ARGS__)
#define make_pw_(...) make_pw__(GCC warning #__VA_ARGS__)
#define make_pw(...) make_pw_(__VA_ARGS__)
#define xmacro(a, b, c) puts("I'm working: " #a #b #c "!")
#define dmacro(a, b, c) make_pw(deprecation_message(dmacro)) xmacro(a, b, c)
int
main(void)
{
dmacro(x, y, z);
return EXIT_SUCCESS;
}
Now, the above code will expand this with clang:
int
main(void)
{
#pragma GCC warning "\042dmacro\042 \042 will be removed from <header.h> in the next release\134n\042 \042of $LIBRARY. To use \042 \042dmacro\042 \042, include <moved/header.h> instead.\042"
puts("I'm working: " "x" "y" "z" "!");
return 0 /* Successful exit status. */;
}
and will produce the following warnings on clang:
src/main.c:18:5: warning: "dmacro" " will be removed from <header.h> in the next release\n" "of $LIBRARY. To use " "dmacro" ", include <moved/header.h> instead." [-W#pragma-messages]
dmacro(x, y, z);
^
and this on gcc:
src/main.c:18:13: warning: "dmacro" " will be removed from <header.h> in the next release\n" "of $LIBRARY. To use " "dmacro" ", include <moved/header.h> instead."
dmacro(x, y, z);
^~~~~~~~
Basically all I did was, to put the deprecation message into quotes...
UPDATE1:
Now, the funny thing is, if you remove the stringification from make_pw_, that is:
#define make_pw_(...) make_pw__(GCC warning __VA_ARGS__)
then clang will give you this:
src/main.c:18:5: warning: dmacro will be removed from <header.h> in the next releaseof $LIBRARY. To use dmacro, include <moved/header.h> instead. [-W#pragma-messages]
dmacro(x, y, z);
^
Which is nice, as it does not have the unwanted quotes, however GCC will only give you this:
src/main.c:18:13: warning: dmacro
dmacro(x, y, z);
^~~~~~~~
What is even more strange, is that if you change the deprecation_message to this:
#define deprecation_message(symbol) "" #symbol "..."
then it will give you an empty warning, like it was only using the first non-white-space token from the macro argument.
To be honest with you, I don't know if this is a bug from GCC, or this is the well-defined behavior and clang is doing some extra work to make sense out of this, none the less, this is happening with the latest versions of the two compilers. (If I have to bet, I would say, this is a bug :P)
UPDATE2:
Here is a slightly modified version, which works on both compilers as you described in your comment. The trick is, that the macro DEPRECATED has two stages, instead of one.
#include <stdio.h>
#include <stdlib.h>
#define DEPRECATED_(...) #__VA_ARGS__
#define DEPRECATED(symbol) \
DEPRECATED_(#symbol will be removed from <header.h> in the next release\n \
of $LIBRARY. To use #symbol, include <moved/header.h> instead.)
#define PRAGMA_WARN_(message) _Pragma(#message)
#define PRAGMA_WARN(message) PRAGMA_WARN_(GCC warning message)
#define dmacro(a, b, c) \
PRAGMA_WARN(DEPRECATED(dmacro)) \
puts("I'm working: " #a #b #c "!")
int
main(void)
{
dmacro(x, y, z);
return EXIT_SUCCESS;
}
So this will produce the following output in clang:
src/main.c:19:5: warning: "dmacro" will be removed from <header.h> in the next release of $LIBRARY. To use "dmacro", include <moved/header.h> instead. [-W#pragma-messages]
dmacro(x, y, z);
^
And the following one in GCC:
src/main.c:19:13: warning: "dmacro" will be removed from <header.h> in the next release
of $LIBRARY. To use "dmacro", include <moved/header.h> instead.
dmacro(x, y, z);
^~~~~~~~
NOTE: I would highly recommend you, to remove the \n character from your message!

how to disable this particular warning

This simple code:
#define WIDTH 500.5
#define NB 23.2
int x[(int)(WIDTH/NB)];
gives me a warning:
prog.c:4:1: warning: variably modified 'x' at file scope [enabled by default]
If I set #define WIDTH 500 and #define NB 23, the warning disappears.
Passing a float value for WIDTH macro forces evaluation by the compiler and thus issues a warning because array has not a constant size.
The preprocessed C code looks like int x[(int)(500.5/23.2)];, whereas int x[(int)(500/23)]; is OK for the compiler (value is already constant integer)
I would like to find a way either
to ignore this particular warning (but leaving the others so I can enable -Werror: seems that is a lost cause: GCC, C: Finding out name of default warnings for use in #pragma ignore
fix the code so it does what I want without issuing warnings.
force the pre-processor to perform the computation as integer
Funny thing: compiling with g++ I don't have the warning whereas I read here that variable length arrays are not officially supported in C++, only in C99. But that's not an option for me, since I need to stick to C.
It just violates the standard:
Integer constant expression
An integer constant expression is an
expression that consists only of operators other than assignment,
increment, decrement, function-call, or comma, except that cast
operators can only cast arithmetic types to integer types, integer
constants, enumeration constants, character constants, floating
constants, but only if they are immediately used as operands of casts
to integer type
And further:
The following contexts require expressions that are known as integer
constant expressions':
...
The index in an array designator (since C99)
One could ensure that a normal array (non VLA) is created like this:
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
enum{ARRAY_DIMENSION_OF_x = (int)(WIDTH/NB)};
int x[ARRAY_DIMENSION_OF_x];
#pragma GCC diagnostic pop
This compiles without warning in gcc and clang in C mode and with a old-style cast to 'int' warning in C++ mode.
One could also hide the creation of the enum within a macro:
/* ================= define MAKE_CONST_INT() macro ================= */
#ifdef __cplusplus
template<class T> struct make_const_int_helper{static T t;};
# define MAKE_CONST_INT(x) (sizeof(*(make_const_int_helper<char (*)[int((x)+0L)]>::t))) /* +0L avoids "useless cast" warning*/
#else
# define EVALUATE_TO_0(type) (0*__builtin_types_compatible_p(type,int))
# define EVALUATE_TO_0_PRAGMA(x) EVALUATE_TO_0(struct{int dummy; _Pragma(x)})
# define EVALUATE_TO_0_START_DISABLE_WARNINGS \
( EVALUATE_TO_0_PRAGMA("GCC diagnostic push") \
+ EVALUATE_TO_0_PRAGMA("GCC diagnostic ignored \"-Wpedantic\"") \
+ EVALUATE_TO_0_PRAGMA("GCC diagnostic ignored \"-Wc++-compat\"") )
# define EVALUATE_TO_0_END_DISABLE_WARNINGS \
EVALUATE_TO_0_PRAGMA("GCC diagnostic pop")
# define MAKE_CONST_INT(x) MAKE_CONST_INT_HELPER2(x,__LINE__,__COUNTER__)
# define MAKE_CONST_INT_HELPER2(x,line,counter) MAKE_CONST_INT_HELPER(x,line,counter)
# define MAKE_CONST_INT_HELPER(x,line,counter) \
( EVALUATE_TO_0_START_DISABLE_WARNINGS \
+ EVALUATE_TO_0(enum{ INT_CONSTANT_##counter##_AT_LINE_##line = (int)(x) }) \
+ INT_CONSTANT_##counter##_AT_LINE_##line \
+ EVALUATE_TO_0_END_DISABLE_WARNINGS)
#endif
/* ================= test MAKE_CONST_INT() macro ================= */
#define WIDTH 500.5
#define NB 23.2
extern int x[MAKE_CONST_INT(WIDTH/NB)];
This compiles without warning in gcc and clang in C and in C++ mode.
The structs with the int dummy members are only necessary to get a place where gcc accepts a _Pragma("GCC diagnostic ....") pragma.
Example
Extended implementation that works for MSVC, gcc, clang and icc, but still won't compile on really picky compilers:
/* ================= define MAKE_CONST_INT() macro ================= */
#ifdef __cplusplus
# if defined(__GNUC__) && !defined(__clang__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wtemplates"
# endif
template<class T> struct make_const_int_helper{static T t;};
# if defined(__GNUC__) && !defined(__clang__)
# pragma GCC diagnostic pop
# endif
# define MAKE_CONST_INT(x) (sizeof(*(make_const_int_helper<char (*)[int((x)+0L)]>::t))) /* +0L avoids "useless cast" warning*/
#else
# if defined(__GNUC__)
# define EVALUATE_TO_0(type) (0*__builtin_types_compatible_p(type,int))
# define EVALUATE_TO_0_PRAGMA(x) EVALUATE_TO_0(struct{int dummy; _Pragma(x)})
# define EVALUATE_TO_0_START_DISABLE_WARNINGS \
( EVALUATE_TO_0_PRAGMA("GCC diagnostic push") \
+ EVALUATE_TO_0_PRAGMA("GCC diagnostic ignored \"-Wpedantic\"") \
+ EVALUATE_TO_0_PRAGMA("GCC diagnostic ignored \"-Wc++-compat\"") )
# define EVALUATE_TO_0_END_DISABLE_WARNINGS \
EVALUATE_TO_0_PRAGMA("GCC diagnostic pop")
# else
# define EVALUATE_TO_0(type) (0*sizeof(type))
# if defined(_MSC_VER)
# define EVALUATE_TO_0_START_DISABLE_WARNINGS \
(0 __pragma(warning( push )) __pragma(warning(disable:4116)))
# define EVALUATE_TO_0_END_DISABLE_WARNINGS (0 __pragma(warning( pop )) )
# else
# define EVALUATE_TO_0_START_DISABLE_WARNINGS 0 /*other compilers will not disable warning*/
# define EVALUATE_TO_0_END_DISABLE_WARNINGS 0
# endif
# endif
# define MAKE_CONST_INT(x) MAKE_CONST_INT_HELPER2(x,__LINE__,__COUNTER__)
# define MAKE_CONST_INT_HELPER2(x,line,counter) MAKE_CONST_INT_HELPER(x,line,counter)
# define MAKE_CONST_INT_HELPER(x,line,counter) \
( EVALUATE_TO_0_START_DISABLE_WARNINGS \
+ EVALUATE_TO_0(enum{ INT_CONSTANT_##counter##_AT_LINE_##line = (int)(x) }) \
+ INT_CONSTANT_##counter##_AT_LINE_##line \
+ EVALUATE_TO_0_END_DISABLE_WARNINGS)
#endif
/* ================= test MAKE_CONST_INT() macro ================= */
#define WIDTH 500.5
#define NB 23.2
extern int x[MAKE_CONST_INT(WIDTH/NB)];
Here one example from GNU GCC that can help you:
#pragma GCC diagnostic error "-Wuninitialized"
foo(a); /* error is given for this one */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wuninitialized"
foo(b); /* no diagnostic for this one */
#pragma GCC diagnostic pop
foo(c); /* error is given for this one */
#pragma GCC diagnostic pop
foo(d); /* depends on command-line options */

Warning about incorrect packing pragma

How can I get GCC or Clang to warn me about the error below, where M was supposed to be N in line 2?
Even with -Wall -Wextra -Wunknown-pragmas, none of these compilers emit any sort of warning.
#define N 4
#pragma pack(push, M)
int main() {
return 0;
}
#pragma pack(pop)
The compiler cannot know your intentions, only if the code is valid. You can check up on yourself with this
#ifndef M
#error M not defined
#endif
Or if M was defined separately from N with this
#if M != N
#error M is not N
#endif
If you look at the output of the preprocessor the pragma is not evaluated ie if you add N you won't get a 4 after the preprocessor is done.
I don't think you can have it warn here, at least not easily, maybe a preprocessor wizard might be able to figure it out. The following warning does not help unless it's in an undefined value in an #if statement.
-Wundef
If you try and add anything to the pragma ie
#pragma pack(push, (M + 0))
You get a warning because the pragma is expecting an integer or identifier ie
warning: expected integer or identifier in '#pragma pack'

Resources