Search code examples
cgccc-preprocessorpragma

GCC error _Pragma fires if it appears in an intermediate preprocessing step?


The following code, if preprocessed with gcc -E, produces some errors from _Pragma("GCC error"):

_Pragma("GCC error \"ERROR\"") // error

#define MACRO_ERROR _Pragma("GCC error \"MACRO_ERROR\"")
MACRO_ERROR // error

#define VOID(arg)
VOID(_Pragma("GCC error \"VOID_ERROR\"")) // no error

#define MACRO_VOID_ERROR VOID(_Pragma("GCC error \"MACRO_VOID_ERROR\""))
MACRO_VOID_ERROR // no error

#define FORWARD(macro, arg) macro(arg)
FORWARD(VOID, _Pragma("GCC error \"FORWARD_VOID_ERROR\"")) // error

#define MACRO_FORWARD_VOID_ERROR FORWARD(VOID, _Pragma("GCC error \"MACRO_FORWARD_VOID_ERROR\""))
MACRO_FORWARD_VOID_ERROR // error

FORWARD(VOID, _Pragma("GCC error \"FORWARD_VOID_ERROR\"")) and MACRO_FORWARD_VOID_ERROR produce errors although the pragma operator is not in the final expansion (which is empty).

Is this expected behavior?

In comparison, VOID(_Pragma("GCC error \"VOID_ERROR\"")) and MACRO_VOID_ERROR do not produce errors. It seems like this is because the pragma operator is "preprocessed away quick enough" with these.

What is the rule behind this?

Previously, I was assuming that the pragma operator is without an effect if it shows up in intermediate expansion steps only. Obviously that is wrong, at least for my gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.4).

Output of gcc -E (empty lines removed):

# 1 "<stdin>"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "<stdin>"
# 1 "<stdin>"
<stdin>:1:11: error: ERROR
# 1 "<stdin>"
# 4 "<stdin>"
<stdin>:4:11: error: MACRO_ERROR
# 4 "<stdin>"
# 13 "<stdin>"
<stdin>:13:11: error: FORWARD_VOID_ERROR
# 13 "<stdin>"
# 16 "<stdin>"
<stdin>:16:11: error: MACRO_FORWARD_VOID_ERROR
# 16 "<stdin>"

Solution

  • This actually is as expected (or at least, what I would expect), although it's unintuitive because you're mixing expression-level side effects into a language that was originally designed to be completely side-effect-free and partially lazy.

    Referring to the C standard rather than to GCC's documentation, we can find the following in 6.10.3.1:

    A parameter in the replacement list, unless preceded by a # or ## preprocessing token or followed by a ## preprocessing token (see below), is replaced by the corresponding argument after all macros contained therein have been expanded. Before being substituted, each argument’s preprocessing tokens are completely macro replaced as if they formed the rest of the preprocessing file; no other preprocessing tokens are available.

    The key piece of ambiguous phrasing here is that the standard only says that an argument is expanded ("as if it formed the rest of the file") before being substituted. It does not explicitly say that an argument needs to be expanded if it isn't going to be substituted at all, and since the first part of the paragraph makes it clear that how an argument is handled depends on the context in which it is actually used by the macro, this is a fair optimization to make (pretty necessary for handling complex metaprogramming libraries like Boost, as well).

    "As if they formed the rest of the file" is key for why the execution of the _Pragma operator should happen before the substitution into an argument: 5.1.1.2 lists _Pragma execution as belonging the same phase as macro replacement, so if replacement is applied to a given token sequence, which it definitely is when the argument is substituted, so should _Pragma execution.

    So it is completely unsurprising that the _Pragma should run in the FORWARD case, because it needs to be evaluated, and its effects applied, two steps before the VOID macro is picked up for expansion during rescan. It's potentially ambiguous whether it should be applied in the case of the direct call to VOID, but since the standard explicitly has macros decide how to handle an argument based on how the argument is used, it makes sense that it should be allowed for an argument that is completely unused to never be evaluated at all, even though this is not specified directly.