Check if a constructed constant is #define'd

守給你的承諾、 提交于 2021-01-28 13:46:24

问题


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 #defined 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):

  1. ... it may contain unary operator expressions of the form

    defined identifier
    

    or

    defined ( identifier )
    

and

  1. 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 #defineing 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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!