【问题标题】:C Macro to Create Bitfield Defines用于创建位域定义的 C 宏
【发布时间】:2015-07-06 18:28:07
【问题描述】:

问题

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

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

【问题讨论】:

  • cpp 不能生成宏!使用代码生成器(例如用 Python 编写)生成适当的标头。
  • 我在这里看不到任何位域。
  • 制作一个生成内联函数的宏。
  • @ddriver: SO 11815894 很有趣,但最受好评的答案是 C++ 仅在实践中,尽管可以修改这些想法以使其与 C 一起使用。

标签: c macros c-preprocessor


【解决方案1】:

宏不能定义另一个宏,但它可以调用另一个宏(预处理器在没有非终结符之前不会停止)

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

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

【讨论】:

  • 请将sm 括在括号中,这样如果有人尝试#define AnotherMask(x) SHIFTMASK(x, NODE&amp;7, 0x3C | 0x04),事情就不会发生混乱。但这对于改进一个非常明智的答案来说是琐事。
  • 好吧,我想了想,这似乎不是原始发帖人的用例——但仍然添加它们,因为它们不会造成伤害。
  • 同意,原始用例没有问题,但代码很少只在原始用例中使用,并且使其(更接近)防弹是最低成本 - 两对括号.
  • 你说的完全正确,我应该从我的工作中知道......下次我会做“防弹”编辑而不等待评论;)
  • 您提出了一个很好的观点,即不必定义新的宏。 Jonathan 的解决方案是我最终使用的解决方案,但这肯定会在我们仍然使用 C89 的旧平台上有用。
【解决方案2】:

作为Artsuggested,一种可能性是使用宏来创建内联函数:

#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 Palmensuggests那样做——这比建议的预编译更好更简单。我很想使用内联函数,只要它们按预期工作,但 Felix 的建议是如果它们不使用,应该使用什么。

【讨论】:

  • 您的编辑真是太棒了……当您可以像这样利用预处理器时,为什么还要依赖外部工具?非常好!我必须记住这个技巧。
  • 第一次调用对我来说是使用static inline,虽然编辑中的方法也很漂亮。必须把它放在我的后口袋里以备不时之需。
【解决方案3】:

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

这些宏处理解释:

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

错误地声明位范围会导致编译错误,所以这也是一个相当安全的方法。

【讨论】:

    【解决方案4】:

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

    【讨论】:

      猜你喜欢
      • 2010-12-08
      • 2012-03-13
      • 1970-01-01
      • 2010-10-22
      • 1970-01-01
      • 2023-03-24
      相关资源
      最近更新 更多