Zephyr的宏UTIL_LISTIFY分析

Creative Commons
本作品采用知识共享署名

本文分析Zephyr一个用于代码生成的宏UTIL_LISTIFY。

前两周有一位群里的朋友私信问Zephyr内的一个宏UTIL_LISTIFY是如何实现代码生成的,当时大致看了下基本也就是一个变参的宏展开。在Zephyr大量使用了类似的宏,来实现代码生成,乍一看神秘,细看弯弯绕绕,但不外乎就是宏的展开。本文就以UTIL_LISTIFY进行分析看如何展开

人力分析

我们先来看下UTIL_LISTIFY的注释

1
2
3
4
5
6
7
8
9
* Example:
*
* #define FOO(i, _) MY_PWM ## i ,
* { UTIL_LISTIFY(PWM_COUNT, FOO) }
*
* The above two lines expand to:
*
* { MY_PWM0 , MY_PWM1 , }
*

大概功能就是UTIL_LISTIFY会根据第一个参数PWM_COUNT的大小,实现MY_PWM_X序列的生成,生成序列的元素个数由PWM_COUNT决定,例如上面例子就是2。
这个宏在Zephyr中不少地方都用过,这里就以USB中生成HID device信息变量为例子进行分析:
代码在ubsys/usb/class/hid/core.c

1
UTIL_LISTIFY(CONFIG_USB_HID_DEVICE_COUNT, DEFINE_HID_DEV_DATA, _)

1. 首先在include/sys/util_macro.h找到UTIL_LISTIFY定义

1
#define UTIL_LISTIFY(LEN, F, ...) UTIL_CAT(Z_UTIL_LISTIFY_, LEN)(F, __VA_ARGS__)

我们假设配置了3个HID device,将CONFIG_USB_HID_DEVICE_COUNT=3,DEFINE_HID_DEV_DATA,_替换展开得到

1
UTIL_CAT(Z_UTIL_LISTIFY_, 3)(DEFINE_HID_DEV_DATA, _)

2. 在include/sys/util_internal.h找到UTIL_CAT定义

1
#define UTIL_CAT(a, ...) UTIL_PRIMITIVE_CAT(a, __VA_ARGS__)

带入后变为

1
UTIL_PRIMITIVE_CAT(Z_UTIL_LISTIFY_, 3)(DEFINE_HID_DEV_DATA, _)

3. 在include/sys/util_internal.h找到UTIL_PRIMITIVE_CAT定义

1
#define UTIL_PRIMITIVE_CAT(a, ...) a##__VA_ARGS__

带入后变为

1
Z_UTIL_LISTIFY_3(DEFINE_HID_DEV_DATA, _)

4. 这里出现了一个新的宏Z_UTIL_LISTIFY_3,可以在include/sys/util_loops.h中找到

1
2
3
4
5
6
7
8
9
10
11
12
#define Z_UTIL_LISTIFY_0(F, ...)

#define Z_UTIL_LISTIFY_1(F, ...) \
F(0, __VA_ARGS__)

#define Z_UTIL_LISTIFY_2(F, ...) \
Z_UTIL_LISTIFY_1(F, __VA_ARGS__) \
F(1, __VA_ARGS__)

#define Z_UTIL_LISTIFY_3(F, ...) \
Z_UTIL_LISTIFY_2(F, __VA_ARGS__) \
F(2, __VA_ARGS__)

发现没有,这里就是一层套一层的展开,达到了代码生成的效果

1
Z_UTIL_LISTIFY_3(DEFINE_HID_DEV_DATA, _)

通过

1
2
#define DEFINE_HID_DEV_DATA(x, _)					\
struct hid_device_info usb_hid_dev_data_##x;

展开第一层得到了usb_hid_dev_data_2

1
Z_UTIL_LISTIFY_2(DEFINE_HID_DEV_DATA, _) struct hid_device_info usb_hid_dev_data_2;

再继续展开第二次得到了

1
Z_UTIL_LISTIFY_1(DEFINE_HID_DEV_DATA, _) struct hid_device_info usb_hid_dev_data_1; struct hid_device_info usb_hid_dev_data_2;

再展开第三次得到了

1
struct hid_device_info usb_hid_dev_data_0; struct hid_device_info usb_hid_dev_data_1; struct hid_device_info usb_hid_dev_data_2;

到这里就完成根据config的数据完成代码的生成,非常巧妙的运用了宏。更进一步可以看util_loops.h最多可以支持到256个生成Z_UTIL_LISTIFY_256,从256一层层套到0。

依靠编译器

Zephyr有大量这样的宏,虽然很绕,但Zephyr做了很好的注释,看注释就足够帮助理解代码了。不过有时我们想知道宏是如何实现的,难道就要这样一层一层这样展开看吗?当宏代码/参数过多时,如果全靠人力来看难免得不偿失。这里我们完全可以利用GCC的预编译功能,GCC会帮助你进行宏展开,例如我将下面代码保存为macro.c

1
2
3
4
5
6
7
8
9
#define CONFIG_USB_HID_DEVICE_COUNT 3
#define DEFINE_HID_DEV_DATA(x, _) \
struct hid_device_info usb_hid_dev_data_##x;

#define UTIL_PRIMITIVE_CAT(a, ...) a##__VA_ARGS__
#define UTIL_CAT(a, ...) UTIL_PRIMITIVE_CAT(a, __VA_ARGS__)
#define UTIL_LISTIFY(LEN, F, ...) UTIL_CAT(Z_UTIL_LISTIFY_, LEN)(F, __VA_ARGS__)

UTIL_LISTIFY(CONFIG_USB_HID_DEVICE_COUNT, DEFINE_HID_DEV_DATA, _)

执行gcc -E macro.c, 就会自动帮你展开好

1
2
3
4
5
6
7
8
9
# 1 "macro.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "macro.c"
# 14 "macro.c"
Z_UTIL_LISTIFY_3(DEFINE_HID_DEV_DATA, _)

当你想要跟进一步,就加入Z_UTIL_LISTIFY_3的宏定义在进行预编译。当你想一步一步了解,就一个宏定义预编译一次。