宏定义用于创建位域定义

4

问题

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

#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(...)`调用(其中一些字段被广泛使用,导致维护困难)。
最后一个限制:我需要的字段在数百个寄存器中零散分布。如果可能的话,我真的很想避免为每个字段定义一个结构体,因为我通常只需要其中的一个或两个字段。

C++ 不能“生成”宏!使用代码生成器(例如用 Python 编写的)来生成适当的头文件。 - too honest for this site
我这里没有看到任何位域。 - Eugene Sh.
1
制作一个可以生成内联函数的宏。 - Art
@ddriver:SO 11815894很有趣,但是最受欢迎的答案实际上只适用于C++,尽管这些想法可以进行修改以使其与C一起使用。 - Jonathan Leffler
4个回答

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
请将sm都放在括号中,以防止出现这样的情况:如果有人尝试#define AnotherMask(x) SHIFTMASK(x, NODE&7, 0x3C | 0x04),那么事情就会变得混乱不堪。但这只是为了改进一个本来非常明智的答案而进行的琐碎细节。 - Jonathan Leffler
我考虑过这个问题,似乎不是原始帖子的用例,但是还是加上它们,因为它们无妨。 - user2371524
同意,原始用例没有问题,但代码很少只在原始用例中使用,使其(更接近)防弹的成本很小——只需两对括号。 - Jonathan Leffler
你说得很对,我应该从工作中学到这个...下次我会在不等待评论的情况下进行“防弹”编辑 ;) - user2371524
你提出了一个很好的观点,关于不必定义新的宏。我最终采用了Jonathan的解决方案,但这对我们仍在使用C89的旧平台肯定是有用的。 - MysteryMoose
太好了,预处理器的“黑魔法”仍然让我印象深刻 ;) 我选择了这里的直截了当的方式,很感激它得到了赞赏。尽管如此,Jonathan提出的解决方案更接近您的要求。 - user2371524

3

艺术 建议的那样,一种可能性是使用宏来创建内联函数:

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

宏定义和调用可以放在头文件中,尽管如果有特定文件的调用也没有问题。

与将其作为预计算值而不是函数来执行相比,这样做是否会有主要性能损失?

这样做不太可能有任何性能损失,因为对于这么简单的函数,编译器将内联所有代码,就像使用宏一样,并且没有函数开销。是的,你的编译器可能出现问题(或者不支持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那样做 - 这比预编译更好、更简单。只要内联函数按预期工作,我会倾向于使用它们,但如果它们不能按预期工作,那么Felix的建议就是需要使用的。

你的编辑真的很棒...为什么要依赖外部工具,当你可以像这样利用预处理器呢?非常好!我得记住这个技巧。 - user2371524
第一次调用使用 static inline 对我很有效,尽管编辑中的方法也非常棒。将不得不把它放在口袋里备用。 - MysteryMoose

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)

声明位范围不正确会导致编译错误,因此这是一种非常安全的使用方法。

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选项可以抑制输出中所有不必要的“信息”指令行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接