【问题标题】:C preprocessor macro specialisation based on an argument基于参数的 C 预处理器宏特化
【发布时间】:2012-07-22 20:25:25
【问题描述】:

是否可以让一个宏对一个特定的参数值进行不同的扩展,而对所有其他参数进行不同的扩展?

假设我定义了一个当前用户:

#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 匹配时,我希望用户特定的宏扩展为有意义的东西。因为我希望这些宏包含_pragmas,所以它不能进行运行时检查(如下面的一些回答者所建议的)。

编辑:再次澄清。假设有一个宏可以禁用某些代码段的优化:

#define TURN_OPTIMISATION_OFF __pragma optimize("", off)

一些程序员希望关闭不同代码段的优化,但不是一次全部关闭。我想要的是有一个宏:

#define TURN_OPTIMISATION_OFF(user) /* magic */

这将匹配 user 参数与 CURRENT_USER 宏,取自每个程序员的配置文件。如果用户匹配宏则扩展为 pragma。如果没有,那就什么都没有。

【问题讨论】:

  • user 参数从何而来?预处理的复杂器是常数吗?为什么_Pragma 要求排除了“运行时”检查(一个好的优化编译器会在编译时优化!)。你真的应该说更多,显示更多代码,并解释更多。您还应该查看预处理输出以了解预处理器的作用。
  • 您是否考虑过改进您的构建机制(例如您的Makefile-s),以便使用例如调用编译器-DCURRENT_USER_ID=$(shell id -u),假设是带有 GNU 的 Linux 系统 make)?
  • 感谢您的建议,但在这种情况下,我们定义 CURRENT_USER 的位置无关紧要。
  • 您确实应该解释潜在的预处理器技巧的总体目标。它是开发人员特定的优化(我认为这是一个错误)吗?如果是,请说明并告诉我们您的开发系统。
  • @BasileStarynkevitch:我试图让这个问题尽可能抽象。我欢迎您的意见,但将放弃讨论——我没有征求一般性建议。我非常具体地询问了 C 预处理器机制。

标签: c++ c c-preprocessor


【解决方案1】:

你为什么不使用简单的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

【讨论】:

  • 我说过我希望宏以不同的方式扩展。另外,我不希望特定用户有不同的扩展,我希望 当前,未指定用户有不同的扩展。
  • current 是什么?预处理器常量、全局变量还是局部变量?你很困惑,甚至可能很困惑。
【解决方案2】:

如果宏的参数总是非常恒定的(即使是字面上和词法上),你可以用concatenation 来耍花招,比如

#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 tiny 函数:

static inline int current_user(int user)
{
   return (user==THE_USER)?(something()):(somethingelse());
}

(请注意,如果user 是一个常量,也许在之前的一些编译器优化之后,编译器会将其优化为更简单的东西,并且编译后的二进制文件不会在运行时测试user。另请参阅__builtin_constant_p if用gcc编译)。

但我相信您的预处理器技巧可能会降低您的代码的可读性。制作时三思而后行。

而且您没有告诉我们您的确切宏用法是什么。你用它作为左值吗?

正如你所说,预处理器不能扩展到预处理指令,所以你的例子:

#define IS_CURRENT_USER(user)                   \
  /* this is not valid preprocessor macro */  \
  #if user == CURRENT_USER                    \
      1                                       \
  #else                                       \
      0                                       \
  #endif       

(如你所说)不正确。

你只能做这样的事情:

 #if user == CURRENT_USER
 #define IS_CURRENT_USER(U) 1
 #else
 #define IS_CURRENT_USER(u) 0
 #endif

我故意使用 u 而不是 user 作为宏 IS_CURRENT_USER 的形式参数以提高可读性(形式没有扩展,只有它在宏中出现)。

您是否意识到预处理发生在“编译之前”?你跑了吗? gcc -C -E 获取预处理输出?应该有借鉴意义!

阅读更多关于the C preprocessor的信息

顺便说一句,您是否考虑过使用脚本(或您自己的生成器,或autotools,或像@ 这样的通用预处理器)生成一些 C 代码(可能是 #include-d 某处) 987654325@ 或 m4)?您可以生成(例如来自用户群,例如 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 中;我觉得这很难看(而且它没有解决优化可能会改变编码错误程序的全局行为的事实)。

您可以自定义 GCC,例如MELT 扩展提供自定义编译指示或内置以满足您的需求,但我发现在您的特定情况下这很奇怪。

注意。从历史上看,cpp 被设计为一个快速的文本处理器,而不是图灵完备的。在过去(1980-s Unix)它作为一个单独的进程运行,真正的编译由cc1 完成,cc 编译器只是一个驱动它们的 shell 脚本(asld) .今天,gcc 是一个小型驱动程序,但出于性能原因,cc1 包含了预处理器。尽管如此,C 标准被指定,因此预处理可以是一个独立于编译器的程序。

【讨论】:

  • 至于连接,我在原始问题中介绍了它。我还澄清了我打算如何使用这些宏。而且我认为代码生成在这里不是有效的解决方案,因为我需要根据当前用户更改宏在现有代码中的扩展方式。
  • 你当然可以生成一个包含许多预处理器条件的包含 C 文件,例如 #if ;这是 GNU 自动工具的标准做法。
  • 正如我在问题中所述,我不知道所有可能的用户。很不幸,那行不通。
  • 在 Unix 系统上,可能的用户集是已知的(例如来自 /etc/passwd、NIS/YP、LDAP 或 getpwent(3) 库函数)。所以你可以像我建议的那样编写myoptim.h 的生成器。我无法想象一个系统不知道它的用户集......
  • 实际上预处理器可以像图灵机一样非常接近。见stackoverflow.com/a/10526117/375343
【解决方案3】:

预处理发生在编译之前。

如果预处理器知道用户,那么是的:

#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

但这完全没用,我怀疑它是你实际拥有的。

否则,。不要滥用微控制器和预处理器。

修改后:

不,你想要的肯定是不可能的(根据用户关闭优化)。

【讨论】:

  • 用户由CURRENT_USER指定,定义另一个user有什么意义?
  • @gwiazdorrr 没有。这没有任何意义。但这是您想要的实际工作的唯一情况。
  • gwiazdorrr 可以生成头文件,请参阅我的(编辑的)答案。
  • @LuchianGrigore:事实证明它可能的。看看我的回答。
  • @gwiazdorrr 不会让用户进入预处理概念吗?
【解决方案4】:

这样的事情有什么问题?

#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

【讨论】:

  • 我不想为 john_smith 扩展一些不同的东西;它是一个特定的用户。我希望为 CURRENT_USER 指定的用户以不同方式扩展宏。请参阅我在问题中的说明。
【解决方案5】:

首先,您可以使用## 与预处理器进行模式匹配。这就是 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

接下来,可以使用检测技术来检测参数是某个值还是括号。它依赖于扩展为不同数量参数的可变参数。检测的核心是CHECK 宏和PROBE 宏,如下所示:

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

【讨论】:

  • 破解阅读。没有做我要求的一切,但仍然很棒。然而,MSVC 编译器似乎没有正确扩展 CHECK,无论你用什么输入它,它总是生成 0。
  • 另外,IS_PAREN 不是有错误吗? IS_PAREN(xxx) 将扩展为 IS_PAREN_PROBE xxx。不应该定义为CHECK( IS_PAREN_PROBE ## x)吗?
  • 它不适用于 MSVC,它需要 C99 预处理器。不过,有一些解决方法可以让它发挥作用。
  • 好的,已修复 IS_PAREN。它应该是CHECK( IS_PAREN_PROBE x),因为在 C99 中不允许使用标记粘贴 (##) 括号(尽管我认为它在 MSVC 中或多或少会起作用)
【解决方案6】:

看来有可能。此 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 bug,我不得不稍微修改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_<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(感谢保罗!)我决定实现原问题中提到的优化宏:

#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

【讨论】:

    【解决方案7】:

    下面的代码对 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 ,
    

    【讨论】:

    • 将宏定义为逗号 ???所以你可以将逗号作为宏参数传递???所以你可以改变论点的位置???多么令人费解!
    猜你喜欢
    • 2014-04-02
    • 1970-01-01
    • 2016-03-30
    • 2011-12-14
    • 2020-02-09
    • 1970-01-01
    • 2016-07-29
    • 2013-03-14
    • 2021-05-05
    相关资源
    最近更新 更多