用于创建位域定义的 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); }

lsbmask 需要括号,以防有人喜欢这种调用。

然后您可以根据需要创建函数:

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 是显而易见的选择,但您可以使用任何您喜欢的东西(awkperlpythonruby — 它们中的任何一个都可以使用过)。

或者您可以像 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)

不正确地声明位范围会导致编译错误,所以这也是一种非常安全的方法。