30年前我念大学时从一个朋友那里学来的一个技巧。

它是汇编语言的一个宏,但很容易转换为C语言宏。

我一直在使用它,但有意思的是我还从没在别人的代码中看到过。现在该我把这个小技巧传递下去了。

让我们举个陈腐的栗子。假设我们有一个头文件叫color.h,里面有一个颜色的宏:

enum Color { Cred, Cblue, Cgreen };

在相应的源文件color.c中,为了正确的打印颜色,有一个字符串数组:

static char *ColorStrings[] = {"red", "blue", "green"};

我们可以这样使用:

enum Color c;
...
printf("the color is %s\n",
ColorStrings[c]);

到目前为止一切都很好。随着时间推移,假如新加入一个颜色:

enum Color{ Cred, Cyellow, Cblue, Cgreen };

是的,假如我们忘记更新数组ColorStrings[]了,打印Cyellow却输出了“blue”,更糟糕的是,如果打印Cgreen会造成数组越界。

(作为一个聪明的程序员,你是不可能犯这样的错误的,对么?)

主要问题是在enum和数组之间没有语义连接。 通常的解决办法是添加一个单元测试包。

但如果我们能找到一个连接enum和数组的方法,从而在编译时检测到此类错误,岂不美哉?

---  X 宏 ---

这是它的功能么?

它能做到这一点么?

X宏如下:

#define COLORS \
  X(Cred, "red") \
  X(Cblue, "blue") \
  X(Cgreen, "green")

把这个放在color.h中。接下来的是颜色枚举的定义:

#define X(a, b) a,
  enum Color { COLORS };
#undef X

在源代码文件color.c中这样定义数组:

#define X(a, b) b,
  static char *ColorStrings[] = { COLORS };
#undef X

可以看出,我们重新定义了X宏,以便提取出必要的信息而忽略其它。

正确的宏管理在这里得以体现,因为如果X已经定义过#define X将会抱怨,而#undef保证了这一点不会发生。

现在如果再添加一个颜色将变得非常简单:

#define COLORS \
  X(Cred, "red") \
  X(Cyellow, "yellow") \
  X(Cblue, "blue") \
  X(Cgreen, "green")

enum和数组都自动得到了更新,看起来很美妙是不是。有经验的程序员会立刻明白可以有更复杂的设计:

#define COLORS \
  X(red) \
  X(blue) \
  X(green) #define X(a) C##a,
  enum Color { COLORS };
#undef X #define X(a) #a,
  static char *ColorStrings[] = { COLORS };
#undef X

一个真实的例子是在C++编译器 Digital Mars 前端:

#define ENUMSCMAC \
  X(unde, SCEXP|SCKEP|SCSCT ) \
  X(auto, SCEXP|SCSS|SCRD ) \
  X(static, SCEXP|SCKEP|SCSCT) \
  X(thread, SCEXP|SCKEP ) \
  ...

3个独立但并行构造的构建 - 枚举,用于打印的字符串表,以及数组。

我使用过的最复杂的X宏有6个参数,它可以构造枚举,结构初始化,运行时初始化等。

当然,你可能已经在使用一个叫X的宏或者变量,且在宏内部X是硬编码的。

Andrei Alexandrescu(Author of Modern C++ Design)建议以下改进,即将X宏作为参数:

#define FOR_ALL_COLORS(apply) \
  apply(red) \
  apply(blue) \
  apply(green)

紧接着:

#define SELECT_STRING(a) #a,
static char *ColorStrings[] =
{
  FOR_ALL_COLORS(SELECT_STRING)
};
#undef SELECT_STRING

任何语言只要支持文本宏预处理程序,X宏技术就能大展身手。

C语言肯定能胜任工作。使用并且传播它,就像我贴心的朋友把他告诉我一样。:)

就像之前说的那样,我还从来没看见过其他人使用这个技巧。因为它晦涩难懂么?欢迎评论。

原文

扩展1

扩展2

编辑

X Macro的更多相关文章

  1. FreeMarker学习(宏<#macro>的使用)

    原文链接:https://my.oschina.net/weiweiblog/blog/506301?p=1 用户定义指令-使用@符合来调用  有两种不同的类型:Macro(宏)和transform( ...

  2. configure.ac:32: error: possibly undefined macro: AC_DEFINE

    在ubuntu 下编译snappy时,在检查依赖关系时,处理autoconf的包时,在相关依赖包都已经安装的情况下,报如下错误,死活不过. configure.ac:32: error: possib ...

  3. 【freemaker】之自定义指令<#macro>

    测试代码 @Test public void test07(){ try { root.put("name", "张三"); freemakerUtil.fpr ...

  4. C++ macro(宏)使用小结

    谈起C++中的宏,我们第一个想到的应该就是“#define”,它的基本语法长得像这样: #define macroname(para1, para2, para3, ... ,paran) macro ...

  5. Macro and SQL

    If you’ve developed anything in the supply chain area, you’ve most probably come across InventDimJoi ...

  6. The difference between macro and function I/Ofunction comparision(from c and pointer )

    macro is typeless and execute faster than funtion ,becaus of the overhead of calling and returnning ...

  7. Cmockery macro demo hacking

    /********************************************************************* * Cmockery macro demo hacking ...

  8. __KERNEL__ macro

    转载:http://blog.csdn.net/kasalyn/article/details/17097639 The __KERNEL__ macro is defined because the ...

  9. 幾種方法實現C語言Macro for debug

    1. #include <stdio.h> #include <stdlib.h> #define DEBUG 1 #ifdef DEBUG #define DEBUG_PRI ...

  10. jinja2 宏的简单使用总结(macro)

    Table of Contents 1. 简介 2. 用法 3. 参数和变量 4. 注意事项 4.1. macro的变量只能为如下三种: 4.2. 和block的关系: 5. 参考文档 1 简介 ji ...

随机推荐

  1. ReactiveX

    http://reactivex.io The real power comes with the “reactive extensions” (hence “ReactiveX”) — operat ...

  2. 实现数组类(C++ 拷贝构造函数、拷贝函数)要判断赋值左右对象不相等,坑惨了

    #include <iostream> using namespace std; class ArrayIndexOutOfBoundsException{ // 异常类 public: ...

  3. python tips:文件读取——换行符的问题

    问题:在windows系统中,换行的符号是'\r\n'.python在读文件的时候为了系统兼容,会默认把'\r','n','\r\n'都视作换行.但是在windows文件中,可能在同一行中同时存在'\ ...

  4. windows端口被占用解决办法

    1.查找端口 netstat -ano | findstr 端口号 2.进程列表并查找相应的进程 tasklist |findstr 进程号 3.杀死进程 taskkill /f /t /im 进程名 ...

  5. anaconda下jieba和wordcloud安装

    1.在anaconda交互环境下安装jieba,输入命令:  pip install jieba 2.在https://pypi.python.org/pypi/wordcloud下载wordclou ...

  6. 容器化haproxy+keepalived

    # 拉取haproxy镜像 docker pull haproxy:1.7.8-alpine mkdir /etc/haproxy cat >/etc/haproxy/haproxy.cfg&l ...

  7. CentOS 笔记(五) 常用工具

    远程 :XShell6  ,PuTTy FPT:Xfpt ,pscp.exe

  8. 微信公众号开发之获取微信用户的openID

    (注:openID同一用户同一应用唯一,UnionID同一用户不同应用唯一.不同应用指微信开放平台下的不同用户.) 1.  申请测试号(获得appID.appsecret) 2.  填写服务器配置并验 ...

  9. MongoDB记录(坑在末尾)

    Mongo数据库基本配置 基本配置 密码配置 pymongo认证 参考资料 基本配置 基本配置包括 1.端口号:默认27017,安全性较低 2.数据库文件位置 3.日志文件位置 4.日志写入模式 5. ...

  10. c#远程链接服务器中MySQL

    转自原文 c#远程链接服务器中MySQL 1.要连接MySQL数据库必须首先下载mysql官方的连接.net的文件,文件下载地址为http://dev.mysql.com/downloads/conn ...