C 如何消除冗余宏参数

C 如何消除冗余宏参数,c,gcc,macros,c-preprocessor,x-macros,C,Gcc,Macros,C Preprocessor,X Macros,不久前,我为一个更大的项目写了一套。我需要维护字符串和枚举引用/哈希值/回调函数等的一致列表 #define LREF_LOOKUP_TABLE_TEXT_SIZE 32 #define _LREF_ENUM_LIST(_prefix,_ref,...) _prefix ## _ ## _ref, #define _LREF_BASE_STRUCT_ENTRY(_prefix,_ref) .text= #_ref "\0", .position= _LREF_ENUM_LIST(_prefix,

不久前,我为一个更大的项目写了一套。我需要维护字符串和枚举引用/哈希值/回调函数等的一致列表

#define LREF_LOOKUP_TABLE_TEXT_SIZE 32
#define _LREF_ENUM_LIST(_prefix,_ref,...) _prefix ## _ ## _ref,
#define _LREF_BASE_STRUCT_ENTRY(_prefix,_ref) .text= #_ref "\0", .position= _LREF_ENUM_LIST(_prefix, _ref)
#define _LREF_FUNCTION_STRUCT_LIST(_prefix,_ref,...) {_LREF_BASE_STRUCT_ENTRY(_prefix,_ref) _prefix ## _ ## _ref ## _callback},

#define _LREF_ENUM_TYPEDEF(_prefix)                                               \ 
    typedef enum _prefix                                                          \  
    {                                                                             \  
        _ ## _prefix ## s(_prefix,_LREF_ENUM_LIST)                                \ 
        _LREF_ENUM_LIST(_prefix,tblEnd)                                           \ 
    } e_ ## _prefix

#define _LREF_LOOKUP_TABLE_TYPEDEF(_prefix, _extras)                              \ 
    typedef struct _prefix ## _lookup                                             \ 
    {                                                                             \ 
        const char text[LREF_LOOKUP_TABLE_TEXT_SIZE];                             \ 
        e_ ## _prefix position;                                                   \ 
        _extras                                                                   \ 
    } _prefix ##_lookup_t

#define LREF_GENERIC_LOOKUP_TABLE(_prefix, _type, _tabledef, _listdef, _extras)   \ 
    _LREF_ENUM_TYPEDEF(_prefix);                                                  \ 
    _LREF_LOOKUP_TABLE_TYPEDEF(_prefix,_tabledef);                                \ 
    _extras                                                                       \ 
    _LREF_LOOKUP_TABLE_DECLARATION(_prefix,_listdef, _type)

#define LREF_FUNCTION_LOOKUP_TABLE(_prefix, _type)                                \ 
    _ ## _prefix ## s(_prefix, _LREF_FUNCTION_DEF )                               \ 
    LREF_GENERIC_LOOKUP_TABLE(    _prefix,                                        \ 
        _type,                                                                    \ 
        void* (*function) (void*);,                                               \ 
    _LREF_FUNCTION_STRUCT_LIST,  )
它位于头文件中,允许我编写以下内容:

#define _cl_tags(x,_)         \
    _(x, command_list)        \
    _(x, command)             \
    _(x, parameter)           \
    _(x, fixed_parameter)     \
    _(x, parameter_group)     \
    _(x, group)               \ 
    _(x, map)                 \
    _(x, transform)

LREF_FUNCTION_LOOKUP_TABLE(cl_tag, static);
这使得处理例程很短。例如,加载带有上述标记的配置文件非常简单:

for (node_tag = cl_tag_lookup_table; node_tag->position != cl_tag_tblEnd; node_tag++)
{
    if (strcasecmp(test_string, node_tag->text) == 0)
    {
        func_return = node_tag->function((void*)m_parser);
    }
}
我的问题是:我讨厌在我的
#define
中包含第二个参数。我希望能够编写
#define(定义)cl(标记)
而不是
#define(定义)cl(标记)
。如您所见,
x
仅用于向下传递前缀(cl_标记)。但这是多余的,因为前缀是初始宏的参数

如果我的预处理器首先展开最外层的宏,那么解决这个问题就很容易了。不幸的是,GCC的预处理器在扩展最外层的宏之前先处理最内层的宏,即参数值

我使用的是GCC4.4.5


澄清 根据C89(和C99)标准,以下定义

#define plus(x,y) add(y,x)
#define add(x,y) ((x)+(y))
随着召唤

plus(plus(a,b),c)
应该让步

  • plus(plus(a,b,c)
  • 添加(c,加上(a,b))
  • ((c)+(加上(a,b))
  • ((c)+(添加(b,a))
  • ((c)+((b)+(a)))
  • gcc 4.4.5给出

  • plus(plus(a,b,c)
  • plus(添加(b,a),c)
  • plus((b)+(a)),c)
  • 添加(c,((b)+(a)))
  • ((c)+((b)+(a)))
  • 以下是我将要做的(类似的):

    将这些文件放在实用程序头文件中:

    /*
     * Concatenate preprocessor tokens A and B without expanding macro definitions
     * (however, if invoked from a macro, macro arguments are expanded).
     */
    #define PPCAT_NX(A, B) A ## B
    
    /*
     * Concatenate preprocessor tokens A and B after macro-expanding them.
     */
    #define PPCAT(A, B) PPCAT_NX(A, B)
    
    #define LREF_TAG cl_tag
    
    然后在包含LREF宏头文件之前定义:

    /*
     * Concatenate preprocessor tokens A and B without expanding macro definitions
     * (however, if invoked from a macro, macro arguments are expanded).
     */
    #define PPCAT_NX(A, B) A ## B
    
    /*
     * Concatenate preprocessor tokens A and B after macro-expanding them.
     */
    #define PPCAT(A, B) PPCAT_NX(A, B)
    
    #define LREF_TAG cl_tag
    
    然后,在LREF宏头文件中

    #define LREF_PFX(x) PPCAT(LREF_TAG, x)
    #define LREF_SFX(x) PPCAT(x, LREF_TAG)
    
    然后用
    LREF\u PFX(foo)
    替换
    #u前缀#foo
    的每个实例,用
    LREF\u SFX(foo)
    替换
    foo#u前缀

    (将两个以上的令牌粘贴在一起时,只需使用嵌套的PPCAT即可。)

    你的祈祷会变成

    #define LREF_TAG cl_tag
    #define _cl_tags(_)        \
        _(command_list)        \
        _(command)             \
        _(parameter)           \
        _(fixed_parameter)     \
        _(parameter_group)     \
        _(group)               \ 
        _(map)                 \
        _(transform)
    
    LREF_FUNCTION_LOOKUP_TABLE(static);
    

    这个答案只是针对“澄清”。以下是正确的行为:

    #define plus(x,y) add(y,x)
    #define add(x,y) ((x)+(y))
    
    Initial:   plus(plus(a,b),c)
    Pass 1a:   plus(add(b,a),c)
    Pass 1b:   add(c,add(b,a))
    Pass 2a:   add(c,((b)+(a)))
    Pass 2b:   ((c)+(((b)+(a))))
    
    规则是,每个宏以非递归方式被替换一次(嵌套时从最内部开始);然后一个新过程(也称为“重新扫描”)重复相同的过程,直到过程不执行替换为止


    我不知道你想说什么,因为你对GCC和你期望发生的事情都给出了相同的最终结论。

    谢谢,但这会给一个更大的问题带来麻烦。在这里,
    LREF\u标签
    需要在
    LREF功能查找表
    (顺便说一句,应该保留这两个参数),然后在调用后取消定义,以避免与将来的查找表发生冲突。@Seth我几乎看不出这是一个更大的问题。如果代码是适度模块化的,那么每个文件只应该有一个LREF_标记设置。至于“顺便保留两个参数”--并非如我所述:该宏中的所有_prefix实例都将被LREF_标记替换。但是,如果你不想这样做,你可以自由保留你所拥有的。我感谢你的回答,并不想诋毁它。我上述评论的意思是,
    define LREF_标记
    增加了一个额外的约束(您描述的模块化)。理想情况下,
    LREF_FUNCTION_LOOKUP_TABLE
    调用应该描述它正在创建的查找表,以便保留代码的自文档性质。@Seth如果选择在一个级别将cl_标记作为参数传递,则必须在每个级别传递它。唯一可能的选择是在某个点设置变量,但无法综合#define或任何其他预处理器命令。因此,设置变量的唯一方法是手动操作,如我的回答所示。底线是,如果拒绝唯一可行的解决方案,则提问是毫无意义的。“GCC的预处理器工作…”根据C标准,宏扩展非常精确。如果是这样的话。引用Harbison和Steele v.5 p.50“在扫描宏调用时,宏替换也不会在functionlike宏的实际参数标记字符串中执行。”将其与GCC的CPP文档进行比较:宏参数在被替换到宏体之前是完全宏扩展的,除非它们被字符串化或用其他标记粘贴。替换后,整个宏体(包括被替换的参数)将再次扫描以查找要扩展的宏。“我当时在X3J11上,致力于宏观扩展规则的制定,我很确定gcc遵循标准。这两个文本并不矛盾——H&S所说的是(没有)在调用站点扫描参数期间进行宏替换,而gcc文档讨论的是宏主体内的扩展。如果宏替换是在调用站点的参数中进行的,则字符串化或标记粘贴的例外情况将不可能出现。另外,请参阅下面我的PPCAT_NX和PPCAT宏,其中xemplify这些规则并处理所有符合要求的实现。您的“应该屈服”扩展不正确-这不是标准要求的。GCC给出的扩展是正确的-这是标准要求的。