警告

本 Blog 仅供蒟蒻 tiger0132 复习用。本蒟蒻

不对其内容正确性做任何保证。

若发现 Bug 请在评论区反馈。本蒟蒻欢迎一切形式的贡献。

点击屏幕以关闭。

「乱搞」玩宏心得

RT,写一些 C 宏的小 trick。

这个宏是 C 的宏,不是 Robin 的宏(

本文部分参考 http://feng.zone 的 Blog 宏定义黑魔法-从入门到奇技淫巧 系列。有部分修改。

宏的语法

定义方式

宏有下列几种定义方式:

  • #define identifier replacement-list new-line
  • #define identifier (identifier-list[opt]) replacement-list new-line
  • #define identifier (...) replacement-list new-line
  • #define identifier (identifier-list, ...) replacement-list new-line

其中第一种形式被称为 object-like macro,即 常量宏。其它的形式被称为 function-like macro,即 函数宏

注意,[opt] 是指这个参数可以忽略(optional),而不是指宏的一部分。

replacement list 是宏的 替换列表identifier list 是宏的 标识符列表。它们被称之为 列表 是因为它们其实是 token 的列表,而不是字符串。比如下面这个例子:

1
2
3
4
5
#define A a    b
#define p(x) #x
#define pp(x) p(x)
#include <cstdio>
int main() { puts(p(a b)); puts(pp(A)); }

输出是

1
2
a b
a b

宏的解析方式

在一个函数宏的标识符列表中,且宏参数出现在了 ## 的两端,那么标识符列表中的这个参数将会被替换成它的值。如果这个参数是空的,那么会被替换为 placemarker token。例如:

1
2
3
4
#define t(x,y,z) x ## y ## z
int j[] = { t(1,2,3), t(,4,5), t(6,,7), t(8,9,), t(10,,), t(,11,), t(,,12), t(,,) };
// =>
int j[] = { 123, 45, 67, 89, 10, 11, 12, };

然后下面是 obj-like 宏的替换规则:

  1. 所有在替换列表中出现的宏参数被替换,且 ### 已经处理完毕,且所有的 placemarker token 已经删除后,然后重新扫描生成的预处理 token 序列以及源文件的所有后续预处理标记,以替换更多的宏名称。

  2. 如果在扫描标识符列表的时候,找到了要展开的宏的名字,那么不作处理;

    如果在递归展开宏的时候,遇到了上层宏的名字,那么同样不作处理。

例如:

一些简单的小 trick

展开参数

假设你想有一个 debug 宏:#define PRINT(x) puts(#x)

然后你写了一份这样的代码:

1
2
3
4
#include <cstdio>
#define PRINT(x) puts(#x)
#define QWQ(x) x##2
int main() { PRINT(QWQ(2)); }

你希望它的展开方式是这样的:PRINT(QWQ(2)) => PRINT(22) => puts("22")

但是它是这样的:PRINT(QWQ(2)) => puts("QWQ(2)");

1
2
3
4
5
#include <cstdio>
#define PRINT_0(x) puts(#x)
#define PRINT(x) PRINT_0(x)
#define QWQ(x) x##2
int main() { PRINT(QWQ(2)); }

这时它的展开方式是:PRINT(QWQ(2)) => PRINT_0(QWQ(2)) =>

获取指定下标参数 (弱化版)

1
#define GET_2ND