定义一个扩展为可变数量元素的C宏

时间:2015-03-31 21:18:48

标签: c macros c-preprocessor

我正在编写USB报告描述符,它是一个字节序列:一个标记字节(其中低位表示后面有多少数据字节),后跟0,1,2或4个数据字节。例如定义输入的逻辑范围:

uint8_t report_descriptor[] = {
    ...
    0x15, 0x00,                     //   Logical Minimum (0)
    0x26, 0xFF, 0x03,               //   Logical Maximum (1023)
    ...
};

由于0适合一个字节,我们使用标记类型0x15(逻辑最小值为一个数据字节)。但1023需要两个字节,因此标记类型为0x26(逻辑最大值为两个数据字节)。

我原本希望定义一些宏来使其更具可读性(并避免必须对每一行进行注释):

uint8_t report_descriptor[] = {
    ...
    LOGICAL_MINIMUM(0),
    LOGICAL_MAXIMUM(1023),
    ...
};

然而,我遇到了麻烦:该宏需要根据值扩展到不同数量的元素。我没有看到任何简单的方法来实现这一目标。我尝试过像value > 255 ? (value & 0xFF, value >> 8) : value这样的技巧,但它总是扩展到只有一个字节。

我认为规范只允许使用4字节标签,但这样做会很浪费,所以我宁愿不这样做。

我可以使用预处理器吗?

2 个答案:

答案 0 :(得分:2)

有一个脏黑客可以实现所要求的功能。但作为一个肮脏的黑客,它不太可能提高可读性。但它的确有效。首先,让我们像这样定义一个包含文件helper.h

#if PARAM > 255
0x26, (PARAM & 0xFF), (PARAM >> 8),
#else
0x15, (PARAM),
#endif

然后在我们的主要内容中我们将做:

uint8_t report_descriptor[] = {

        #define PARAM 0
        #include "helper.h"
        #undef PARAM

        #define PARAM 1023
        #include "helper.h"
        #undef PARAM

};

要看到它在这里工作的是测试代码:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

uint8_t report_descriptor[] = {

        #define PARAM 0
        #include "helper.h"
        #undef PARAM

        #define PARAM 1023
        #include "helper.h"
        #undef PARAM

};


int main(int argc, char** args) {

    int i;
    for (i=0; i < sizeof(report_descriptor); i++ )
        printf("%x\n", report_descriptor[i]);
    return 0;
}

,输出为:

15
0
26
ff
3

答案 1 :(得分:1)

我认为C预处理器不够强大,无法以干净的方式执行此操作。如果您愿意使用M4宏处理器,可以相当容易地完成。 M4应该可以在绝大多数GNU / Linux系统上使用,并且大多数平台都可以使用便携式实现。

让我们在一个单独的文件中定义M4宏,并将其命名为macros.m4

define(`EXTRACT_BYTE', `(($1 >> (8 * $2)) & 0xFF)')

dnl You probably don't want to define these as M4 macros but as C preprocessor
dnl macros in your header files.

define(`TAG_1_BYTES', `0x15')
define(`TAG_2_BYTES', `0x26')
define(`TAG_3_BYTES', `0x37')
define(`TAG_4_BYTES', `0x48')

define(`EXPAND_1_BYTES', `TAG_1_BYTES, EXTRACT_BYTE($1, 0)')
define(`EXPAND_2_BYTES', `TAG_2_BYTES, EXTRACT_BYTE($1, 1), EXTRACT_BYTE($1, 0)')
define(`EXPAND_3_BYTES', `TAG_3_BYTES, EXTRACT_BYTE($1, 2), EXTRACT_BYTE($1, 1), EXTRACT_BYTE($1, 0)')
define(`EXPAND_4_BYTES', `TAG_4_BYTES, EXTRACT_BYTE($1, 3), EXTRACT_BYTE($1, 2), EXTRACT_BYTE($1, 1), EXTRACT_BYTE($1, 0)')

define(`ENCODE',
  `ifelse(eval($1 < 256), `1', `EXPAND_1_BYTES($1)',
    `ifelse(eval($1 < 65536), `1', `EXPAND_2_BYTES($1)',
      `ifelse(eval($1 < 16777216), `1', `EXPAND_3_BYTES($1)',
      `EXPAND_4_BYTES($1)')')')')

现在,编写C文件非常简单。将以下代码放在文件test.c.m4中:

include(`macros.m4')

`static unint8_t report_descriptor[] = {'
    ENCODE(50),
    ENCODE(5000),
    ENCODE(500000),
    ENCODE(50000000),
`};'

Makefile中,添加以下规则

test.c: test.c.m4 macros.m4
    ${M4} $< > $@

其中M4设置为M4处理器(通常为m4)。

如果M4在test.c.m4上运行,它将 - 省略一些多余的空格 - 生成以下test.c文件:

static unint8_t report_descriptor[] = {
  0x15, ((50 >> (8 * 0)) & 0xFF),
  0x26, ((5000 >> (8 * 1)) & 0xFF), ((5000 >> (8 * 0)) & 0xFF),
  0x37, ((500000 >> (8 * 2)) & 0xFF), ((500000 >> (8 * 1)) & 0xFF), ((500000 >> (8 * 0)) & 0xFF),
  0x48, ((50000000 >> (8 * 3)) & 0xFF), ((50000000 >> (8 * 2)) & 0xFF), ((50000000 >> (8 * 1)) & 0xFF), ((50000000 >> (8 * 0)) & 0xFF),
};

您可能会发现将test.c.m4文件尽可能地保持在最小值并将其#include放在普通的C文件中会更方便。

如果你不了解M4,你可以很快地学习基础知识。如果已经使用GNU Autoconf,您可能会发现使用他们的M4sugar M4宏库而不是我上面使用的普通M4很方便。