我正在尝试建立一个测试,以检查某个文件是否定义了具有特定名称空间的标头保护。由于测试是通用的,因此仅在编译时才知道此名称空间,并以-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
),这也将使我的测试管道能够解决此问题。
答案 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 #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.
答案 1 :(得分:3)
我认为在标准C领域中,#ifndef
指令内的宏扩展是不可能的。
#ifndef symbol
等效于#if !defined symbol
。标准对此进行了说明(第6.10.1条有条件包含):
...它可能包含格式为一元的一元运算符
defined identifier
或
defined ( identifier )
和
- 在评估之前,将替换将成为控制常量表达式的预处理令牌列表中的宏调用((由 defined 一元变量修改的那些宏名称除外) 运算符),就像普通文本一样。如果替换产生了定义的令牌 已定义一元运算符的处理或使用与以下两个指定格式之一不匹配 宏替换,其行为是不确定的。 ...
因此,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 #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.