我正在编写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字节标签,但这样做会很浪费,所以我宁愿不这样做。
我可以使用预处理器吗?
答案 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很方便。