首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >X-Macros的实际使用

X-Macros的实际使用
EN

Stack Overflow用户
提问于 2011-07-09 23:56:16
回答 7查看 26.7K关注 0票数 81

我刚了解到X-Macros。你见过X-Macros在现实世界中的哪些用法?什么时候它们才是适合这项工作的工具?

EN

回答 7

Stack Overflow用户

回答已采纳

发布于 2012-02-22 04:13:15

几年前,当我开始在代码中使用函数指针时,我发现了X宏。我是一名嵌入式程序员,经常使用状态机。我经常会写这样的代码:

代码语言:javascript
复制
/* declare an enumeration of state codes */
enum{ STATE0, STATE1, STATE2, ... , STATEX, NUM_STATES};

/* declare a table of function pointers */
p_func_t jumptable[NUM_STATES] = {func0, func1, func2, ... , funcX};

问题是,我认为必须维护函数指针表的顺序以使其与状态枚举的顺序相匹配,这非常容易出错。

我的一个朋友向我介绍了X-宏,它就像一个电灯泡在我的脑海中熄灭了。说真的,我这一生的x宏都到哪里去了!

所以现在我定义下表:

代码语言:javascript
复制
#define STATE_TABLE \
        ENTRY(STATE0, func0) \
        ENTRY(STATE1, func1) \
        ENTRY(STATE2, func2) \
        ...
        ENTRY(STATEX, funcX) \

我可以按如下方式使用它:

代码语言:javascript
复制
enum
{
#define ENTRY(a,b) a,
    STATE_TABLE
#undef ENTRY
    NUM_STATES
};

代码语言:javascript
复制
p_func_t jumptable[NUM_STATES] =
{
#define ENTRY(a,b) b,
    STATE_TABLE
#undef ENTRY
};

另外,我还可以让预处理器构建函数原型,如下所示:

代码语言:javascript
复制
#define ENTRY(a,b) static void b(void);
    STATE_TABLE
#undef ENTRY

另一种用法是声明和初始化寄存器

代码语言:javascript
复制
#define IO_ADDRESS_OFFSET (0x8000)
#define REGISTER_TABLE\
    ENTRY(reg0, IO_ADDRESS_OFFSET + 0, 0x11)\
    ENTRY(reg1, IO_ADDRESS_OFFSET + 1, 0x55)\
    ENTRY(reg2, IO_ADDRESS_OFFSET + 2, 0x1b)\
    ...
    ENTRY(regX, IO_ADDRESS_OFFSET + X, 0x33)\

/* declare the registers (where _at_ is a compiler specific directive) */
#define ENTRY(a, b, c) volatile uint8_t a _at_ b:
    REGISTER_TABLE
#undef ENTRY

/* initialize registers */
#define ENTRY(a, b, c) a = c;
    REGISTER_TABLE
#undef ENTRY

然而,我最喜欢的用法是当涉及到通信处理程序时

首先,我创建了一个通信表,其中包含每个命令名称和代码:

代码语言:javascript
复制
#define COMMAND_TABLE \
    ENTRY(RESERVED,    reserved,    0x00) \
    ENTRY(COMMAND1,    command1,    0x01) \
    ENTRY(COMMAND2,    command2,    0x02) \
    ...
    ENTRY(COMMANDX,    commandX,    0x0X) \

我在表中同时使用了大写和小写的名称,因为大写用于枚举,小写用于函数名。

然后,我还为每个命令定义了结构,以定义每个命令的外观:

代码语言:javascript
复制
typedef struct {...}command1_cmd_t;
typedef struct {...}command2_cmd_t;

etc.

同样,我为每个命令响应定义了struct:

代码语言:javascript
复制
typedef struct {...}command1_resp_t;
typedef struct {...}command2_resp_t;

etc.

然后我可以定义我的命令代码枚举:

代码语言:javascript
复制
enum
{
#define ENTRY(a,b,c) a##_CMD = c,
    COMMAND_TABLE
#undef ENTRY
};

我可以定义我的命令长度枚举:

代码语言:javascript
复制
enum
{
#define ENTRY(a,b,c) a##_CMD_LENGTH = sizeof(b##_cmd_t);
    COMMAND_TABLE
#undef ENTRY
};

我可以定义我的响应长度枚举:

代码语言:javascript
复制
enum
{
#define ENTRY(a,b,c) a##_RESP_LENGTH = sizeof(b##_resp_t);
    COMMAND_TABLE
#undef ENTRY
};

我可以确定有多少命令,如下所示:

代码语言:javascript
复制
typedef struct
{
#define ENTRY(a,b,c) uint8_t b;
    COMMAND_TABLE
#undef ENTRY
} offset_struct_t;

#define NUMBER_OF_COMMANDS sizeof(offset_struct_t)

注意:我实际上从未实例化过offset_struct_t,我只是使用它作为编译器为我生成我的许多命令定义的一种方式。

注意,然后我可以生成我的函数指针表,如下所示:

代码语言:javascript
复制
p_func_t jump_table[NUMBER_OF_COMMANDS] = 
{
#define ENTRY(a,b,c) process_##b,
    COMMAND_TABLE
#undef ENTRY
}

和我的函数原型:

代码语言:javascript
复制
#define ENTRY(a,b,c) void process_##b(void);
    COMMAND_TABLE
#undef ENTRY

最后,为了最酷的使用,我可以让编译器计算我的传输缓冲区应该有多大。

代码语言:javascript
复制
/* reminder the sizeof a union is the size of its largest member */
typedef union
{
#define ENTRY(a,b,c) uint8_t b##_buf[sizeof(b##_cmd_t)];
    COMMAND_TABLE
#undef ENTRY
}tx_buf_t

同样,这个联合类似于我的offset结构,它不是实例化的,相反,我可以使用sizeof操作符来声明我的传输缓冲区大小。

代码语言:javascript
复制
uint8_t tx_buf[sizeof(tx_buf_t)];

现在我的传输缓冲区tx_buf是最佳大小,并且当我向这个通信处理程序添加命令时,我的缓冲区将始终是最佳大小。凉爽的!

另一个用途是创建偏移表:由于内存通常是嵌入式系统的限制,所以当跳转表是稀疏数组时,我不想使用512字节(每个指针2字节×256个可能的命令)。相反,我将为每个可能的命令提供一个包含8位偏移量的表。这个偏移量然后被用来索引到我的实际跳转表中,现在只需要NUM_COMMANDS *sizeof(指针)。在我的例子中,定义了10个命令。我的跳转表是20字节长,我有一个256字节长的偏移表,总共是276字节,而不是512字节。然后我像这样调用我的函数:

代码语言:javascript
复制
jump_table[offset_table[command]]();

而不是

代码语言:javascript
复制
jump_table[command]();

我可以像这样创建一个偏移表:

代码语言:javascript
复制
/* initialize every offset to 0 */
static uint8_t offset_table[256] = {0};

/* for each valid command, initialize the corresponding offset */
#define ENTRY(a,b,c) offset_table[c] = offsetof(offset_struct_t, b);
    COMMAND_TABLE
#undef ENTRY

其中offsetof是在"stddef.h“中定义的标准库宏。

附带的好处是,有一种非常简单的方法来确定命令代码是否受支持:

代码语言:javascript
复制
bool command_is_valid(uint8_t command)
{
    /* return false if not valid, or true (non 0) if valid */
    return offset_table[command];
}

这也是为什么我在COMMAND_TABLE中保留命令字节0的原因。我可以创建一个名为"process_reserved()“的函数,如果使用任何无效的命令字节来索引我的偏移表,就会调用该函数。

票数 104
EN

Stack Overflow用户

发布于 2011-07-10 02:15:48

X-Macros本质上是参数化模板。因此,如果你需要几种不同伪装的类似东西,它们是适合这项工作的工具。它们允许您创建抽象表单并根据不同的规则将其实例化。

我使用X宏将枚举值输出为字符串。由于遇到了它,我非常喜欢这个表单,它接受一个"user“宏来应用于每个元素。使用多个文件包含要痛苦得多。

代码语言:javascript
复制
/* x-macro constructors for error and type
   enums and string tables */
#define AS_BARE(a) a ,
#define AS_STR(a) #a ,

#define ERRORS(_) \
    _(noerror) \
    _(dictfull) _(dictstackoverflow) _(dictstackunderflow) \
    _(execstackoverflow) _(execstackunderflow) _(limitcheck) \
    _(VMerror)
enum err { ERRORS(AS_BARE) };
char *errorname[] = { ERRORS(AS_STR) };
/* puts(errorname[(enum err)limitcheck]); */

我还将它们用于基于对象类型的函数分派。同样,通过劫持我用来创建枚举值的同一个宏。

代码语言:javascript
复制
#define TYPES(_) \
    _(invalid) \
    _(null) \
    _(mark) \
    _(integer) \
    _(real) \
    _(array) \
    _(dict) \
    _(save) \
    _(name) \
    _(string) \
/*enddef TYPES */

#define AS_TYPE(_) _ ## type ,
enum { TYPES(AS_TYPE) };

使用宏可以保证我的所有数组索引都将匹配相关的枚举值,因为它们使用宏定义( TYPES宏)中的裸标记构造各种形式。

代码语言:javascript
复制
typedef void evalfunc(context *ctx);

void evalquit(context *ctx) { ++ctx->quit; }

void evalpop(context *ctx) { (void)pop(ctx->lo, adrent(ctx->lo, OS)); }

void evalpush(context *ctx) {
    push(ctx->lo, adrent(ctx->lo, OS),
            pop(ctx->lo, adrent(ctx->lo, ES)));
}

evalfunc *evalinvalid = evalquit;
evalfunc *evalmark = evalpop;
evalfunc *evalnull = evalpop;
evalfunc *evalinteger = evalpush;
evalfunc *evalreal = evalpush;
evalfunc *evalsave = evalpush;
evalfunc *evaldict = evalpush;
evalfunc *evalstring = evalpush;
evalfunc *evalname = evalpush;

evalfunc *evaltype[stringtype/*last type in enum*/+1];
#define AS_EVALINIT(_) evaltype[_ ## type] = eval ## _ ;
void initevaltype(void) {
    TYPES(AS_EVALINIT)
}

void eval(context *ctx) {
    unsigned ades = adrent(ctx->lo, ES);
    object t = top(ctx->lo, ades, 0);
    if ( isx(t) ) /* if executable */
        evaltype[type(t)](ctx);  /* <--- the payoff is this line here! */
    else
        evalpush(ctx);
}

以这种方式使用X-宏实际上有助于编译器给出有用的错误消息。我在上面省略了evalarray函数,因为它会分散我的注意力。但是,如果您尝试编译上述代码(当然,注释掉其他函数调用,并为上下文提供一个虚拟的typedef ),编译器将会报告缺少函数。对于我添加的每个新类型,当我重新编译此模块时,系统会提醒我添加一个处理程序。因此,X-宏有助于保证并行结构即使在项目增长时也保持不变。

编辑:

这个答案使我的声誉提高了50%。所以这里有更多。下面是一个的反面例子,回答了这个问题: when not to use X-Macros?

这个例子展示了如何将任意代码片段打包到X-“记录”中。我最终放弃了这个项目的这个分支,并且在后来的设计中没有使用这个策略(并不是因为没有尝试)。不知何故,它变得不奇怪了。实际上,宏之所以被命名为X6,是因为有一段时间有6个参数,但我厌倦了更改宏名。

代码语言:javascript
复制
/* Object types */
/* "'X'" macros for Object type definitions, declarations and initializers */
// a                      b            c              d
// enum,                  string,      union member,  printf d
#define OBJECT_TYPES \
X6(    nulltype,        "null",     int dummy      ,            ("<null>")) \
X6(    marktype,        "mark",     int dummy2      ,           ("<mark>")) \
X6( integertype,     "integer",     int  i,     ("%d",o.i)) \
X6( booleantype,     "boolean",     bool b,     (o.b?"true":"false")) \
X6(    realtype,        "real",     float f,        ("%f",o.f)) \
X6(    nametype,        "name",     int  n,     ("%s%s", \
        (o.flags & Fxflag)?"":"/", names[o.n])) \
X6(  stringtype,      "string",     char *s,        ("%s",o.s)) \
X6(    filetype,        "file",     FILE *file,     ("<file %p>",(void *)o.file)) \
X6(   arraytype,       "array",     Object *a,      ("<array %u>",o.length)) \
X6(    dicttype,        "dict",     struct s_pair *d, ("<dict %u>",o.length)) \
X6(operatortype,    "operator",     void (*o)(),    ("<op>")) \

#define X6(a, b, c, d) #a,
char *typestring[] = { OBJECT_TYPES };
#undef X6

// the Object type
//forward reference so s_object can contain s_objects
typedef struct s_object Object;

// the s_object structure:
// a bit convoluted, but it boils down to four members:
// type, flags, length, and payload (union of type-specific data)
// the first named union member is integer, so a simple literal object
// can be created on the fly:
// Object o = {integertype,0,0,4028}; //create an int object, value: 4028
// Object nl = {nulltype,0,0,0};
struct s_object {
#define X6(a, b, c, d) a,
    enum e_type { OBJECT_TYPES } type;
#undef X6
unsigned int flags;
#define Fread  1
#define Fwrite 2
#define Fexec  4
#define Fxflag 8
size_t length; //for lint, was: unsigned int
#define X6(a, b, c, d) c;
    union { OBJECT_TYPES };
#undef X6
};

一个大问题是printf格式的字符串。虽然它看起来很酷,但它只是一个骗局。因为它只在一个函数中使用,过度使用宏实际上分离了应该放在一起的信息;而且它使函数本身不可读。在像这样的调试函数中,混淆是非常不幸的。

代码语言:javascript
复制
//print the object using the type's format specifier from the macro
//used by O_equal (ps: =) and O_equalequal (ps: ==)
void printobject(Object o) {
    switch (o.type) {
#define X6(a, b, c, d) \
        case a: printf d; break;
OBJECT_TYPES
#undef X6
    }
}

所以不要得意忘形。就像我一样。

票数 41
EN

Stack Overflow用户

发布于 2011-07-10 00:28:46

流行项目和大型项目对X-Macros的一些实际使用:

Java HotSpot

在Oracle编程语言的HotSpot虚拟机中,有一个文件globals.hpp,它以这种方式使用RUNTIME_FLAGS

请看源代码:

  • JDK 7
  • JDK 8
  • JDK 9

list of network errors in net_error_list.h是一个很长很长的宏扩展列表,其形式如下:

代码语言:javascript
复制
NET_ERROR(IO_PENDING, -1)

它由同一目录中的net_errors.h使用:

代码语言:javascript
复制
enum Error {
  OK = 0,

#define NET_ERROR(label, value) ERR_ ## label = value,
#include "net/base/net_error_list.h"
#undef NET_ERROR
};

这个预处理器魔术的结果是:

代码语言:javascript
复制
enum Error {
  OK = 0,
  ERR_IO_PENDING = -1,
};

我不喜欢这种特殊用法的是,常量的名称是通过添加ERR_动态创建的。在本例中,NET_ERROR(IO_PENDING, -100)定义了常量ERR_IO_PENDING

使用简单的文本搜索ERR_IO_PENDING,不可能看到它在哪里定义了这个常量。取而代之的是,要找到定义,必须搜索IO_PENDING。这使得代码很难导航,因此增加了整个代码库的obfuscation

票数 8
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/6635851

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档