基于参数的C预处理器宏专业化
是否有可能为一个特定的参数值以不同的方式扩展一个宏,对所有其他参数的扩展方式不同?
假设我定义了一个当前用户:
#define CURRENT_USER john_smith
我想要做的是有一个宏,如果用户传递匹配CURRENT_USER
将扩展不同。 请注意,我不知道所有可能的用户先验。 最基本的案例:
#define IS_CURRENT_USER(user) \ /* this is not valid preprocessor macro */ \ #if user == CURRENT_USER \ 1 \ #else \ 0 \ #endif
像宏一样,依赖于用户名的每个其他宏都可以按照以下方式完成:
#define SOME_USER_SPECIFIC_MACRO(user) SOME_USER_SPECIFIC_MACRO_SWITCH_1(IS_CURRENT_USER(user)) #define SOME_USER_SPECIFIC_MACRO_SWITCH_1(switch) SOME_USER_SPECIFIC_MACRO_SWITCH_2(switch) // expand switch ... #define SOME_USER_SPECIFIC_MACRO_SWITCH_2(switch) SOME_USER_SPECIFIC_MACRO_##switch // ... and select specific case #define SOME_USER_SPECIFIC_MACRO_0 ... // not current user #define SOME_USER_SPECIFIC_MACRO_1 ... // current user
这可能吗?
编辑:让我澄清一下。 假设每个程序员在其配置头中定义了不同的CURRENT_USER
。 当且仅当user
参数与CURRENT_USER
匹配时,我希望用户特定的宏扩展到有意义的东西。 因为我希望这些宏包含_pragma
s,所以它不能进行运行时检查(如下面的某些anwsers中所提出的)。
编辑:再次,澄清。 假设有宏来禁用某些代码段的优化:
#define TURN_OPTIMISATION_OFF __pragma optimize("", off)
一些程序员希望为代码的不同部分关闭优化,但不能同时关闭。 我想要的是拥有一个宏:
#define TURN_OPTIMISATION_OFF(user) /* magic */
这将匹配来自每个程序员配置文件的CURRENT_USER
宏的user
参数。 如果用户匹配宏则扩展为pragma。 如果不是,没有。
首先,您可以使用##
与预处理器进行模式匹配。 这就是如何定义IIF
宏:
#define IIF(cond) IIF_ ## cond #define IIF_0(t, f) f #define IIF_1(t, f) t
但是这种方法存在一个问题。 ##
运算符的一个微妙的副作用是它抑制了扩展。 下面是一个例子:
#define A() 1 //This correctly expands to true IIF(1)(true, false) // This will however expand to IIF_A()(true, false) // This is because A() doesn't expand to 1, // because its inhibited by the ## operator IIF(A())(true, false)
解决这个问题的方法是使用另一个间接方法。 感觉这是常见的,我们可以编写一个名为CAT
的宏,它将连接而不会被禁止。
#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__) #define PRIMITIVE_CAT(a, ...) a ## __VA_ARGS__
那么现在我们可以编写IIF
宏:
#define IIF(c) PRIMITIVE_CAT(IIF_, c) #define IIF_0(t, ...) __VA_ARGS__ #define IIF_1(t, ...) t #define A() 1 //This correctly expands to true IIF(1)(true, false) // And this will also now correctly expand to true IIF(A())(true, false)
通过模式匹配,我们可以定义其他操作,例如COMPL
,它采用补码:
// A complement operator #define COMPL(b) PRIMITIVE_CAT(COMPL_, b) #define COMPL_0 1 #define COMPL_1 0 // An and operator #define BITAND(x) PRIMITIVE_CAT(BITAND_, x) #define BITAND_0(y) 0 #define BITAND_1(y) y
接下来,可以使用检测技术来检测参数是否是特定值或者是否是括号。 它依赖于扩展到不同数量参数的vardiac参数。 检测的核心是带有PROBE
宏的CHECK
宏,如下所示:
#define CHECK_N(x, n, ...) n #define CHECK(...) CHECK_N(__VA_ARGS__, 0,) #define PROBE(x) x, 1,
这很简单。 将探测器提供给CHECK
宏时,如下所示:
CHECK(PROBE(~)) // Expands to 1
但是如果我们给它一个令牌:
CHECK(xxx) // Expands to 0
因此,我们可以创建一些检测宏。 例如,如果我们想要检测括号:
#define IS_PAREN(x) CHECK(IS_PAREN_PROBE x) #define IS_PAREN_PROBE(...) PROBE(~) IS_PAREN(()) // Expands to 1 IS_PAREN(xxx) // Expands to 0
接下来,我们需要对两个令牌进行比较,我们可以依赖宏不递归扩展的事实。 我们强制宏在另一个宏内递归扩展。 如果两个令牌是相同的,那么它将递归地扩展宏,我们将通过尝试检测它们是否扩展到括号来检测,这里是COMPARE
宏:
#define COMPARE(a, b) PRIMITIVE_COMPARE(a, b) #define PRIMITIVE_COMPARE(a, b) \ IIF( \ BITAND \ (IS_PAREN(COMPARE_ ## a(()))) \ (IS_PAREN(COMPARE_ ## b(()))) \ )( \ COMPL(IS_PAREN( \ COMPARE_ ## a( \ COMPARE_ ## b \ )(()) \ )), \ 0 \ ) \
您要比较的每个标记都将如下定义:
// So you would define one for each user #define COMPARE_john_smith(x) x #define COMPARE_another_user_name(x) x
现在,我还没有完全理解你想要生成的最终输出,所以说你有一个用于为当前用户生成代码的宏和一个用于其他用户的宏:
#define MACRO_CURRENT_USER(user) ... #define MACRO_OTHER_USER(user) ...
然后你可以写这样的东西:
// Detects if its the current user #define IS_CURRENT_USER(user) COMPARE(user, CURRENT_USER) // Your macro #define MACRO(user) IIF(IS_CURRENT_USER(user), MACRO_CURRENT_USER, MACRO_OTHER_USER)(user)
希望有所帮助。
调出它是可能的 。 这个anwser基于Pauls宏,但更简单,不需要为每个用户定义。
#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__) #define PRIMITIVE_CAT(a, ...) a ## __VA_ARGS__ #define IIF(c) PRIMITIVE_CAT(IIF_, c) #define IIF_0(t, ...) __VA_ARGS__ #define IIF_1(t, ...) t #define PROBE(x) x, 1
现在,由于MSVC错误,我不得不稍微修改CHECK
宏。
#define MSVC_VA_ARGS_WORKAROUND(define, args) define args #define CHECK(...) MSVC_VA_ARGS_WORKAROUND(CHECK_N, (__VA_ARGS__, 0)) #define CHECK_N(x, n, ...) n
而不是定义CURRENT_USER
我切换到以下宏。
#define ENABLE_USER_gwiazdorrr () // gwiazdorrr is now enabled #define ENABLE_USER_foo () // foo is also enabled // #define ENABLE_USER_bar () // bar is NOT enabled
它实际上提供了更大的灵活性,因为可以同时启用多个用户。 括号是必需的。 下面的宏实际上检测到, ENABLE_USER_
是否扩展为括号。
#define USER_ENABLED_PROBE(user) USER_ENABLED_PROBE_PROXY( ENABLE_USER_##user ) // concatenate prefix with user name #define USER_ENABLED_PROBE_PROXY(...) USER_ENABLED_PROBE_PRIMIVIE(__VA_ARGS__) // expand arguments #define USER_ENABLED_PROBE_PRIMIVIE(x) USER_ENABLED_PROBE_COMBINE_##x // merge #define USER_ENABLED_PROBE_COMBINE_(...) PROBE(~) // if merge successful, expand to probe USER_ENABLED_PROBE(gwiazdorrr) // expands to ~, 1 USER_ENABLED_PROBE(bar) // expands to USER_ENABLED_PROBE_COMBINE_bar
从现在开始,这是一个孩子们的游戏:
#define IS_USER_ENABLED(user) CHECK(USER_ENABLED_PROBE(user)) IS_USER_ENABLED(gwiazdorrr) // expands to 1 IS_USER_ENABLED(bar) // expands to 0
拥有这个宏和IIF
(感谢Paul!)我决定实现原始问题中提到的优化宏:
#define TURN_OPTIMISATION_OFF(user) IIF( IS_USER_ENABLED(user) ) \ (\ __pragma optimize("", off),\ /* nothing */ \ ) TURN_OPTIMISATION_OFF(gwiazdorrr) // expands into __pragma optimize("", off) TURN_OPTIMISATION_OFF(foo) // expands into __pragma optimize("", off) TURN_OPTIMISATION_OFF(bar) // nothing emitted
感谢您的投入!
编辑:这是GCC版本: http : //ideone.com/129eo
预编译在编译之前进行。
如果预处理器知道用户,则是:
#define user 4 #define CURRENT_USER 4 #define IS_CURRENT_USER 1 #if user == CURRENT_USER #define IS_CURRENT_USER(user) 1 #else #define IS_CURRENT_USER(user) 0 #endif
但这完全没用,我怀疑这是你实际拥有的。
否则, 没有 。 不要滥用mecros和预处理器。
编辑后:
不,你想要的绝对不可能(根据用户关闭优化)。
如果宏的参数总是非常不变(甚至字面上和词法上)你可以用连接来玩弄技巧,比如
#define SOME_MACRO(T) SOME_MACRO_FOR_##T #define SOME_MACRO_FOR_0 somethinghere() #define SOME_MACRO_FOR_1 somethingelse()
否则,你可以
#define CURRENT_USER ((user == THE_USER)?(something()):(somethingelse()))
或者使用static inline
微小function:
static inline int current_user(int user) { return (user==THE_USER)?(something()):(somethingelse()); }
(请注意,如果user
是常量,也许在之前的一些编译器优化之后,编译器会将其优化为更简单的东西,并且编译的二进制文件不会在运行时测试user
。如果使用gcc
编译,请参阅__builtin_constant_p )。
但我相信你的预处理器技巧可能会降低你的代码的可读性。 制作它们时要三思而后行。
而且您没有告诉我们您的确切宏用法是什么。 你用它作为左值吗?
正如您所说,预处理器无法扩展到预处理指令,因此您的示例如下:
#define IS_CURRENT_USER(user) \ /* this is not valid preprocessor macro */ \ #if user == CURRENT_USER \ 1 \ #else \ 0 \ #endif
是(正如你所说)不正确。
你只能做以下事情:
#ïf user == CURRENT_USER #define IS_CURRENT_USER(U) 1 #else #define IS_CURRENT_USER(u) 0 #endif
我故意使用u
不是user
作为你的宏IS_CURRENT_USER
的正式参数以便于阅读(forms上没有扩展,只有它在宏中出现)。
你是否意识到预处理是在“编译之前”发生的? 您是否运行了例如gcc -C -E
来获得预处理的输出? 它应该是有益的!
阅读有关C预处理器的更多信息
顺便说一句,您是否考虑使用脚本(或您自己的生成器,或autotools ,或autogen或m4等通用预处理器)生成一些C代码(可能是#include
-d某处)? 您可以生成 (例如来自Linux上的/etc/passwd
用户群,或NIS / YP,LDAP或getpwent(3)
…) #include
-d myoptim.h
#if CURRENT_USER_ID==1234 #define OPTIMIZATION_FOR_PAUL _pragma(GCC(optimize,"-O1")) #else #define OPTIMIZATION_FOR_PAUL /*nothing*/ #endif #if CURRENT_USER_ID==3456 #define OPTIMIZATION_FOR_ALICE _pragma(GCC(optimize,"-O1")) #else #define OPTIMIZATION_FOR_ALICE /*nothing*/ #endif
并且请Paul(假设他的uid是1234)在他的函数前加上OPTIMIZATION_FOR_PAUL
,并将CFLAGS=-DCURRENT_USER_ID=$(shell id -u)
放在Makefile
; 我发现这很丑陋(并没有解决优化可能改变不良编码程序的全局行为的事实)。
您可以使用例如MELT扩展来定制GCC,提供自定义编译指示或内置以满足您的需求,但我发现在您的特定情况下很奇怪。
NB。 从历史上看, cpp
被设计成一个快速的文本处理器,而不是图灵完备。 在过去(1980-s Unix),它作为一个单独的进程运行, cc1
完成了真正的编译,而cc
编译器只是一个驱动它们的shell脚本(使用as
和ld
)。 今天, gcc
是一个小型驱动程序,但由于性能原因, cc1
采用了预处理器。 仍然,指定C标准,以便预处理可以是与编译器本身分开的程序。
下面的代码对MSVC错误不敏感。 ……参数没有分开。
#define IF_USER_ENABLED(x,...) IF_USER_ARGS_GT2 (ENABLE_USER_ ## x,__VA_ARGS__) #define IF_USER_ARGS_GT2(x,...) ARGS_ARG2 (x,GT4,3,,__VA_ARGS__) #define ARGS_ARG2(x,y,z,...) ARGS_ ## z (x,y,z,__VA_ARGS__) #define ARGS_3(x,y,z,w,...) w #define ARGS_GT4(x,y,z,w,v,...) __VA_ARGS__ #define IF_USER_DISABLED(x,...) IF_NOT_USER_ARGS_GT2 (ENABLE_USER_ ## x,__VA_ARGS__) #define IF_NOT_USER_ARGS_GT2(x,...) ARGS_ARG2 (x,4,GT3,,__VA_ARGS__) #define ARGS_4(x,y,z,w,v,...) v #define ARGS_GT3(x,y,z,w,...) __VA_ARGS__ #define ENABLE_USER_foo , //#define ENABLE_USER_bar ,
你为什么不简单地使用if
语句? #if
不能在宏中使用。
这是一个例子:
// Return 1 if `user' is the current user, 0 else. #define IS_CURRENT_USER(user) ((user) == CURRENT_USER)
或者,如果在编译期间设置了USER
,则可以减少条件分支的数量。
#if USER == CURRENT_USER # define IS_CURRENT_USER (1) #else # define IS_CURRENT_USER (0) #endif
这样的事情出了什么问题?
#if CURRENT_USER == john_smith #define SOME_USER_SPECIFIC_MACRO_SWITCH WHATEVER ## CURRENT_USER #define SOME_USER_SPECIFIC_MACRO_1 ... #else #define SOME_USER_SPECIFIC_MACRO_0 ... #define SOME_USER_SPECIFIC_MACRO_SWITCH WHATEVER ## Somethingelse #endif// not current user