C宏创建位域定义

时间:2015-07-06 18:28:07

标签: c macros c-preprocessor

问题

我正在进行一些固件编码,并且需要管理很多位掩码。到目前为止,我一直在为每个要掩盖的字段重复相同的块,如下所示:

#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,因为我通常只需要一个或两个字段。

4 个答案:

答案 0 :(得分:5)

宏不能定义另一个宏,但它可以调用另一个宏(预处理器不会停止,直到没有非终端为止)

那么,这样的事情怎么样?至少节省一些打字......

#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;
}

答案 1 :(得分:3)

作为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);

宏定义和调用可以放在标题中 - 尽管如果对特定文件有本地调用也没有坏处。

  

作为一个函数执行此操作是否有任何重大性能影响而不是将其作为预先计算的值?

不太可能出现任何性能损失,因为对于一个简单的函数,编译器将内联所有代码,就像使用宏一样,并且没有函数开销。是的,您的编译器可能已损坏(或者不支持C99或C11 - inline已添加到C99)。说“获得更好的编译器”很诱人。测试,测量,但不应该有开销。

如果这确实是一个问题,那么你必须依靠间接替代方案。例如,您可以创建一个包含以下内容的文件bitfields.hdr

#define BIT_HASH #
#define BIT_FIELD(name, lsb, mask) \
        BIT_HASH define name(value) (((value) >> (lsb)) & (mask))

BIT_FIELD(LinkState, 8, 0x1)

然后您可以安排将其编译成头文件:

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执行 - 这比建议的预编译更好,更简单。只要它们按预期工作,我就会想要使用内联函数,但是菲利克斯的建议是如果不这样做就会使用。

答案 2 :(得分:1)

另一个可能更好的技巧是使用三元运算符定义位字段。

这些宏处理解释:

// 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)

错误地声明位范围将导致编译错误,因此这也是一种非常安全的方法。

答案 3 :(得分: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)

可以轻松地将哪些附加字段添加到表中。

cpp -P收益:

 # define LinkState (((x) >> (8)) & (0x1))

-P选项会抑制输出中所有不需要的“information”指令行。