检查构造的常量是否为#define

时间:2019-03-19 14:15:28

标签: c c-preprocessor

我正在尝试建立一个测试,以检查某个文件是否定义了具有特定名称空间的标头保护。由于测试是通用的,因此仅在编译时才知道此名称空间,并以-DTHENAMESPACE=BLA的形式传入。然后,我们使用https://stackoverflow.com/a/1489985/1711232中的一些魔法将它们粘贴在一起。

这意味着我想做类似的事情:

#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

但这无法正常工作,因为cpp抱怨ifndef不希望使用括号。

如果有可能,我该如何正确地做到这一点?

我也尝试过添加更多的间接层,但是没有取得很大的成功。


直接,正确执行#ifdef至少似乎是不可能的:

考虑defined运算符:

  

如果定义的运算符是由于宏扩展而出现的,则 C标准表示行为未定义。 GNU cpp将其视为真正的定义运算符,并对其进行正常评估。如果您使用命令行选项-Wpedantic,它将在您的代码使用此功能的任何地方发出警告,因为其他编译器可能会对此进行不同的处理。该警告还可以通过-Wextra启用,也可以通过-Wexpansion-to-defined单独启用。

https://gcc.gnu.org/onlinedocs/cpp/Defined.html#Defined

ifdef预期为MACRO,并且不做进一步扩展。

https://gcc.gnu.org/onlinedocs/cpp/Ifdef.html#Ifdef

但是也许有可能触发“未定义常量”警告(-Wundef),这也将使我的测试管道能够解决此问题。

4 个答案:

答案 0 :(得分:5)

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.

答案 1 :(得分:3)

我认为在标准C领域中,#ifndef指令内的宏扩展是不可能的。

#ifndef symbol等效于#if !defined symbol。标准对此进行了说明(第6.10.1条有条件包含):

  
      
  1. ...它可能包含格式为一元的一元运算符

    defined identifier
    
         

    defined ( identifier )
    
  2.   

  
      
  1. 在评估之前,将替换将成为控制常量表达式的预处理令牌列表中的宏调用((由 defined 一元变量修改的那些宏名称除外)   运算符),就像普通文本一样。如果替换产生了定义的令牌   已定义一元运算符的处理或使用与以下两个指定格式之一不匹配   宏替换,其行为是不确定的。 ...
  2.   

因此,defined表达式中的标识符基本上不会扩展,并且您当前的NAMESPACE(API_H)不是标识符的有效形式。

可能的解决方法是简单地使用:

#if NAMESPACE(API_H) == 0
#  error "namespace not properly defined"
#endif

之所以有效,是因为将不存在的标识符替换为0。这种方法的问题在于,如果将BLA_API_H 定义为0,则会出现错误错误,但是这取决于您的情况。

答案 2 :(得分:1)

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

答案 3 :(得分:0)

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.