问题
I am trying to build a test that checks if a certain file defines a header guard with a certain namespace. Because the test is generic, this namespace is only known at compile-time and passed in as -DTHENAMESPACE=BLA
. We then use some magic from https://stackoverflow.com/a/1489985/1711232 to paste that together.
This means I want to do something like:
#define PASTER(x, y) x##_##y
#define EVALUATOR(x, y) PASTER(x, y)
#define NAMESPACE(fun) EVALUATOR(THENAMESPACE, fun)
#ifndef NAMESPACE(API_H) // evaluates to BLA_API_H
# error "namespace not properly defined"
#endif
But this does not work properly, with cpp complaining about the ifndef
not expecting the parentheses.
How can I do this properly, if it is possible at all?
I have also tried adding more layers of indirection, but not with a lot of success.
So directly, properly executing the #ifdef
this at least appears to not be possible:
Considering the defined
operator:
If the defined operator appears as a result of a macro expansion, the C standard says the behavior is undefined. GNU cpp treats it as a genuine defined operator and evaluates it normally. It will warn wherever your code uses this feature if you use the command-line option -Wpedantic, since other compilers may handle it differently. The warning is also enabled by -Wextra, and can also be enabled individually with -Wexpansion-to-defined.
https://gcc.gnu.org/onlinedocs/cpp/Defined.html#Defined
and ifdef
expects a MACRO, and does not do further expansion.
https://gcc.gnu.org/onlinedocs/cpp/Ifdef.html#Ifdef
But maybe it is possible to trigger an 'undefined constant' warning (-Wundef
), which would also allow my test pipeline to catch this problem.
回答1:
If we assume that include guards always looks like
#define NAME /* no more tokens here */
and if, as you said, any compile time error (rather than #error
exclusively) is acceptable, then you can do following:
#define THENAMESPACE BLA
#define BLA_API_H // Comment out to get a error.
#define CAT(x,y) CAT_(x,y)
#define CAT_(x,y) x##y
#define NAMESPACE(x) static int CAT(UNUSED_,__LINE__) = CAT(CAT(THENAMESPACE,CAT(_,x)),+1);
NAMESPACE(API_H)
Here, NAMESPACE(API_H)
tries to concatenate BLA_API_H
and +
using ##
.
This results in error: pasting "BLA_API_H" and "+" does not give a valid preprocessing token
except if BLA_API_H
is #define
d to 'no tokens'.
In presence of #define BLA_API_H
, NAMESPACE(API_H)
simply becomes
static int UNUSED_/*line number here*/ = +1;
If you settle for a less robust solution, you can even get nice error messages:
#define THENAMESPACE BLA
#define BLA_API_H // Comment out to get a error.
#define TRUTHY_VALUE_X 1
#define CAT(x,y) CAT_(x,y)
#define CAT_(x,y) x##y
#define NAMESPACE(x) CAT(CAT(TRUTHY_VALUE_,CAT(THENAMESPACE,CAT(_,x))),X)
#if !NAMESPACE(API_H)
#error "namespace not properly defined"
#endif
Here, if BLA_API_H
is defined, then #if !NAMESPACE(API_H)
expands to #if 1
.
If BLA_API_H
is not defined, then it expands to #if TRUTHY_VALUE_BLA_API_HX
, and TRUTHY_VALUE_BLA_API_HX
evaluates to false
due to being undefined.
The problem here is that if TRUTHY_VALUE_BLA_API_HX
accidentally turns out to be defined to something truthy, you'll get a false negatie.
回答2:
I don't think that macro expansion inside #ifndef
directive is possible in the realm of standard C.
#ifndef symbol
is equivalent of #if !defined symbol
. Standard says this about it (§6.10.1 Conditional inclusion):
... it may contain unary operator expressions of the form
defined identifier
or
defined ( identifier )
and
- Prior to evaluation, macro invocations in the list of preprocessing tokens that will become the controlling constant expression are replaced (except for those macro names modified by the defined unary operator), just as in normal text. If the token defined is generated as a result of this replacement process or use of the defined unary operator does not match one of the two specified forms prior to macro replacement, the behavior is undefined. ...
So basically identifiers in defined
expression are not expanded, and your current NAMESPACE(API_H)
is not valid form of identifier.
Possible workaround could be to simply use:
#if NAMESPACE(API_H) == 0
# error "namespace not properly defined"
#endif
This works because non-existing identifiers are replaced with 0
. Problem with this approach is that there will be false error if BLA_API_H
is defined as 0
, but depending on your situation that may be acceptable.
回答3:
You can also do this using preprocessor pattern matching.
#define PASTE3(A,B,C) PASTE3_I(A,B,C)
#define PASTE3_I(A,B,C) A##B##C
#define PASTE(A,B) PASTE_I(A,B)
#define PASTE_I(A,B) A##B
#define THIRD(...) THIRD_I(__VA_ARGS__,,,)
#define THIRD_I(A,B,C,...) C
#define EMPTINESS_DETECTOR ,
#if THIRD(PASTE3(EMPTINESS_,PASTE(THENAMESPACE,_API_H),DETECTOR),0,1)
# error "namespace not properly defined"
#endif
This is the same idea as @HolyBlackCat's answer except that it's done in the preprocessor; the inner paste in the expression in the #if
directive generates a token based on THENAMESPACE
, pasting it to your required _API_H
. If that token itself is defined in a macro, it will expand to a placemarker during the PASTE3
operation; this effectively pastes EMPTINESS_
[placemarker] DETECTOR
, which is a macro expanding to a comma. That comma will shift the arguments of the indirect THIRD
, placing 0 there, making the conditional equivalent to #if 0
. Anything else won't shift the arguments, which results in THIRD
selecting 1, which triggers the #error
.
This also makes the same assumption HolyBlackCat's answer makes... that inclusion guards always look like #define BLA_API_H
, but you can accommodate specific alternate styles using expanded pattern matching... for example, if you want to accept inclusion guards like #define BLAH_API_H 1
(who does that?), you could add #define EMPTINESS_1DETECTOR ,
.
回答4:
The language defines no way other than use of the defined
operator or an equivalent to test whether identifiers are defined as macro names. In particular, a preprocessor directive of the form
#ifndef identifier
is equivalent to
#if ! defined identifier
(C11, 6.10.1/5). Similar applies to #ifdef
.
The defined
operator takes a single identifier as its operand, however, not an expression (C11, 6.10.1/1). Moreover, although the expression associated with an #if
directive is macro expanded prior to evaluation, the behavior is undefined if in that context macro expansion produces the token "defined
", and macro names modified by the defined
unary operator are explicitly excluded from expansion (C11, 6.10.1/4).
Thus, although it is possible in many contexts to construct macro names via token pasting, and in such contexts the results are thereafter be subject to macro expansion, the operand of a defined
operator is not such a context. The language therefore defines no way to test whether a constructed or indirectly-specified identifier is defined as a macro name.
HOWEVER, you can avoid relying on defined
if you are in control of all the header guards, and you are willing to deviate slightly from traditional style. Instead of merely #define
ing your header guards, define them to some nonzero integer value, say 1:
#if ! MYPREFIX_SOMEHEADER_H
#define MYPREFIX_SOMEHEADER_H 1
// body of someheader.h ...
#endif
You can then drop the defined
operator from your test expression:
#if ! NAMESPACE(API_H)
# error "namespace not properly defined"
#endif
Do note, however, that the #define
directive has a similar issue: it defines a single identifier, which is not subject to prior macro expansion. Thus, you cannot dynamically construct header guard names. I'm not sure whether you had that in mind, but if you did, then all the foregoping is probably moot.
来源:https://stackoverflow.com/questions/55243068/check-if-a-constructed-constant-is-defined