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

编辑

最新文章

  1. ORACLE数据库存储空间使用情况查询
  2. poj 3177 边连通分量
  3. JavaScript笔记(二)——常用数组、字符串方法的应用
  4. 无U盘安装Linux openSUSE(通过硬盘安装Linux)
  5. HDU 3835 R(N)(枚举)
  6. java算法 蓝桥杯 扶老奶奶街
  7. Android jni 编程4(对基本类型二维整型数组的操作)
  8. oracle 两张关联表执行更新update
  9. C#生成.tlb文件初认识
  10. experiment 3
  11. Zabbix监控——Zabbix自定义用户参数制作监控项
  12. Win10手记-为应用集成SQLite(二)
  13. iOS UI布局-回到顶部
  14. SQL 多行合并一行
  15. New users can not log on Win8
  16. php -- 特殊变量的三种输出
  17. window上安装pymysql
  18. Python中的装饰器的初步理解
  19. IE userdata 原理 应用 详解
  20. 在LinuxMint中对firefox进行手动安装flash插件

热门文章

  1. 2017/01/20 学习笔记 关于修改和重打jar包
  2. Swfit4.0中JSON与模型原生互转(JSONEncoder/JSONDecoder的使用)
  3. html IMG 标签水平居中 ,和图片过大 溢出处理
  4. java 1.8 内存告警问题
  5. mmap,malloc分配随机内存
  6. Selenium 安装与配置及webdriver的API与定位元素
  7. Docker:分布式系统的软件工程革命(上)
  8. Linux系统下的 /etc/fstab 文件解读
  9. js严格模式下判断数据类型
  10. apache 与 nginx的区别