用于创建位域定义的 C 宏
C Macro to Create Bitfield Defines
问题
我正在编写一些固件代码并且有很多位掩码需要管理。到目前为止,我一直在为每个要屏蔽的字段重复相同的块,如下所示:
#define LinkStateMask 0x1
#define LinkStateShift 8
#define LinkState(x) (((x) >> LinkStateShift) & LinkStateMask)
请注意,这些字段通常是多位字段,因此并不总是保证掩码是 0x1
。最终的宏可以在代码中非常易读地使用,这是我非常喜欢的:
if( LinkState(temp) == 0 ) {
// Link is down, return error
return -ENODEV;
}
问题
有没有办法让 C 预处理器为我生成这些宏?我知道预处理器只能在单通道中工作,所以宏不能直接定义另一个,但希望有另一种方法。
理想情况下,我想编写类似于以下内容并使用与上面第二个清单相同的 C 代码:
BIT_FIELD(LinkState, 8, 0x1) // BIT_FIELD(name, lsb, mask)
我的关键是在主 C 文件中保留符号命名的函数式用法,而不必每次我需要生成掩码时都编写整个 BIT_FIELD(...)
调用(其中一些字段是广泛使用,导致维护噩梦)。
最后一个限制:我需要的字段稀疏地分布在数百个寄存器中。如果可能的话,我 真的 想避免为每个字段定义一个 struct
,因为我通常只需要每个字段中的一个或两个字段。
如Art suggested,一种可能是使用宏创建内联函数:
#define BIT_FIELD(name, lsb, mask) \
static inline int name(int value) { return (value >> (lsb)) & (mask); }
lsb
和 mask
需要括号,以防有人喜欢这种调用。
然后您可以根据需要创建函数:
BIT_FIELD(LinkState, 8, 0x1)
并在需要时调用它们:
int x = LinkState(y);
宏定义和调用可以放在 header 中 — 尽管如果调用是特定文件的本地调用也没有坏处。
Is there any major performance hit for doing this as a function versus doing this as a pre-computed value?
不太可能对性能造成任何影响,因为对于如此简单的函数,编译器将内联所有代码,就像您使用宏一样,并且没有函数开销。是的,您的编译器可能已损坏(或不支持 C99 或 C11 — inline
已添加到 C99)。说 "get a better compiler" 很诱人。测试、测量,但不应有开销。
如果这确实是一个问题,那么您必须求助于间接替代方案。例如,您可以创建一个文件 bitfields.hdr
包含:
#define BIT_HASH #
#define BIT_FIELD(name, lsb, mask) \
BIT_HASH define name(value) (((value) >> (lsb)) & (mask))
BIT_FIELD(LinkState, 8, 0x1)
然后您可以安排将其编译成 header 文件:
cpp -E bitfield.hdr > bitfield.h
使用 GCC 5.1.0 的 cpp
,我得到输出:
# 1 "bitfield.hdr"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "bitfield.hdr"
# define LinkState(value) (((value) >> (8)) & (0x1))
然后您可以在工作代码中使用 #include "bitfield.h"
。这是一种简单的代码生成形式。如果您愿意,可以使用替代的宏处理器; m4
是显而易见的选择,但您可以使用任何您喜欢的东西(awk
、perl
、python
、ruby
— 它们中的任何一个都可以使用过)。
或者您可以像 Felix Palmen suggests 那样做 — 这比建议的预编译更好、更简单。我很想使用内联函数,只要它们按预期工作,但 Felix 的建议是如果它们不工作则使用什么。
一个宏不能定义另一个宏,但它可以调用另一个宏(预处理器不会停止,直到没有非- 左侧终端)
那么,这样的事情怎么样?至少节省了一些打字...
#include <stdio.h>
#define SHIFTMASK(x,s,m) (((x) >> (s)) & (m))
#define LinkState(x) SHIFTMASK(x, 8, 0x1)
#define Whatever(x) SHIFTMASK(x, 9, 0x7)
int test[] = { 0x0f00, 0x0a01, 0x0100, 0x0007 };
int main()
{
int i;
for (i = 0; i < 4; ++i)
{
printf("test[%d]: 0x%x\n", i, test[i]);
printf("LinkState(test[%d]): 0x%x\n", i, LinkState(test[i]));
printf("Whatever(test[%d]): 0x%x\n", i, Whatever(test[i]));
}
return 0;
}
将 Jonathan 和 Felix 的答案与我自己最喜欢的预处理器技巧 X-Macros 结合起来得到以下结果。
#define Fields(_) \
_(Link, 0x1, 8) \
/*enddef Fields() */
#define HASH #
#define GenState(name, mask, shift) \
HASH define name ## State (((x) >> (shift)) & (mask))
Fields(GenState)
可以轻松将附加字段添加到 table。
cpp -P
产量:
# define LinkState (((x) >> (8)) & (0x1))
-P
选项抑制输出中所有不需要的 "information" 指令行。
另一个可能更好的技巧是使用三元运算符定义位域。
这些宏处理解释:
// Extract starting and ending bit number from a bit_range,
// defined as the arguments for a ternary operator (low:high).
// Example : #define Least3Bits 0:2
#define BitRange(StartBit, BitCount) (StartBit):(StartBit+BitCount-1)
#define BitRangeStart(bit_range) (1?bit_range)
#define BitRangeEnd(bit_range) (0?bit_range)
#define BitRangeMask(bit_range) ((~0 << BitRangeStart(bit_range)) ^ (~0 << BitRangeEnd(bit_range)))
#define BitRangeShift(bit_range) BitRangeStart(bit_range)
#define BitRangeValue(bit_range, value) (((value) & BitRangeMask(bit_range)) >> BitRangeShift(bit_range))
现在您的示例可以这样声明:
#define LinkState_BitRange BitRange(8, 1)
#define LinkState(value) BitRangeValue(LinkState_BitRange, value)
另一个例子:
#define Whatever_BitRange BitRange(9, 3)
#define Whatever(x) BitRangeValue(Whatever_BitRange, value)
不正确地声明位范围会导致编译错误,所以这也是一种非常安全的方法。
问题
我正在编写一些固件代码并且有很多位掩码需要管理。到目前为止,我一直在为每个要屏蔽的字段重复相同的块,如下所示:
#define LinkStateMask 0x1
#define LinkStateShift 8
#define LinkState(x) (((x) >> LinkStateShift) & LinkStateMask)
请注意,这些字段通常是多位字段,因此并不总是保证掩码是 0x1
。最终的宏可以在代码中非常易读地使用,这是我非常喜欢的:
if( LinkState(temp) == 0 ) {
// Link is down, return error
return -ENODEV;
}
问题
有没有办法让 C 预处理器为我生成这些宏?我知道预处理器只能在单通道中工作,所以宏不能直接定义另一个,但希望有另一种方法。
理想情况下,我想编写类似于以下内容并使用与上面第二个清单相同的 C 代码:
BIT_FIELD(LinkState, 8, 0x1) // BIT_FIELD(name, lsb, mask)
我的关键是在主 C 文件中保留符号命名的函数式用法,而不必每次我需要生成掩码时都编写整个 BIT_FIELD(...)
调用(其中一些字段是广泛使用,导致维护噩梦)。
最后一个限制:我需要的字段稀疏地分布在数百个寄存器中。如果可能的话,我 真的 想避免为每个字段定义一个 struct
,因为我通常只需要每个字段中的一个或两个字段。
如Art suggested,一种可能是使用宏创建内联函数:
#define BIT_FIELD(name, lsb, mask) \
static inline int name(int value) { return (value >> (lsb)) & (mask); }
lsb
和 mask
需要括号,以防有人喜欢这种调用。
然后您可以根据需要创建函数:
BIT_FIELD(LinkState, 8, 0x1)
并在需要时调用它们:
int x = LinkState(y);
宏定义和调用可以放在 header 中 — 尽管如果调用是特定文件的本地调用也没有坏处。
Is there any major performance hit for doing this as a function versus doing this as a pre-computed value?
不太可能对性能造成任何影响,因为对于如此简单的函数,编译器将内联所有代码,就像您使用宏一样,并且没有函数开销。是的,您的编译器可能已损坏(或不支持 C99 或 C11 — inline
已添加到 C99)。说 "get a better compiler" 很诱人。测试、测量,但不应有开销。
如果这确实是一个问题,那么您必须求助于间接替代方案。例如,您可以创建一个文件 bitfields.hdr
包含:
#define BIT_HASH #
#define BIT_FIELD(name, lsb, mask) \
BIT_HASH define name(value) (((value) >> (lsb)) & (mask))
BIT_FIELD(LinkState, 8, 0x1)
然后您可以安排将其编译成 header 文件:
cpp -E bitfield.hdr > bitfield.h
使用 GCC 5.1.0 的 cpp
,我得到输出:
# 1 "bitfield.hdr"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "bitfield.hdr"
# define LinkState(value) (((value) >> (8)) & (0x1))
然后您可以在工作代码中使用 #include "bitfield.h"
。这是一种简单的代码生成形式。如果您愿意,可以使用替代的宏处理器; m4
是显而易见的选择,但您可以使用任何您喜欢的东西(awk
、perl
、python
、ruby
— 它们中的任何一个都可以使用过)。
或者您可以像 Felix Palmen suggests 那样做 — 这比建议的预编译更好、更简单。我很想使用内联函数,只要它们按预期工作,但 Felix 的建议是如果它们不工作则使用什么。
一个宏不能定义另一个宏,但它可以调用另一个宏(预处理器不会停止,直到没有非- 左侧终端)
那么,这样的事情怎么样?至少节省了一些打字...
#include <stdio.h>
#define SHIFTMASK(x,s,m) (((x) >> (s)) & (m))
#define LinkState(x) SHIFTMASK(x, 8, 0x1)
#define Whatever(x) SHIFTMASK(x, 9, 0x7)
int test[] = { 0x0f00, 0x0a01, 0x0100, 0x0007 };
int main()
{
int i;
for (i = 0; i < 4; ++i)
{
printf("test[%d]: 0x%x\n", i, test[i]);
printf("LinkState(test[%d]): 0x%x\n", i, LinkState(test[i]));
printf("Whatever(test[%d]): 0x%x\n", i, Whatever(test[i]));
}
return 0;
}
将 Jonathan 和 Felix 的答案与我自己最喜欢的预处理器技巧 X-Macros 结合起来得到以下结果。
#define Fields(_) \
_(Link, 0x1, 8) \
/*enddef Fields() */
#define HASH #
#define GenState(name, mask, shift) \
HASH define name ## State (((x) >> (shift)) & (mask))
Fields(GenState)
可以轻松将附加字段添加到 table。
cpp -P
产量:
# define LinkState (((x) >> (8)) & (0x1))
-P
选项抑制输出中所有不需要的 "information" 指令行。
另一个可能更好的技巧是使用三元运算符定义位域。
这些宏处理解释:
// Extract starting and ending bit number from a bit_range,
// defined as the arguments for a ternary operator (low:high).
// Example : #define Least3Bits 0:2
#define BitRange(StartBit, BitCount) (StartBit):(StartBit+BitCount-1)
#define BitRangeStart(bit_range) (1?bit_range)
#define BitRangeEnd(bit_range) (0?bit_range)
#define BitRangeMask(bit_range) ((~0 << BitRangeStart(bit_range)) ^ (~0 << BitRangeEnd(bit_range)))
#define BitRangeShift(bit_range) BitRangeStart(bit_range)
#define BitRangeValue(bit_range, value) (((value) & BitRangeMask(bit_range)) >> BitRangeShift(bit_range))
现在您的示例可以这样声明:
#define LinkState_BitRange BitRange(8, 1)
#define LinkState(value) BitRangeValue(LinkState_BitRange, value)
另一个例子:
#define Whatever_BitRange BitRange(9, 3)
#define Whatever(x) BitRangeValue(Whatever_BitRange, value)
不正确地声明位范围会导致编译错误,所以这也是一种非常安全的方法。