具有非连续布局的 C 位域元素

Posted

技术标签:

【中文标题】具有非连续布局的 C 位域元素【英文标题】:C bitfield element with non-contiguous layout 【发布时间】:2012-12-28 02:19:17 【问题描述】:

我正在寻找最优雅的接口上的输入,以放置一个内存映射的寄存器接口,其中目标对象在寄存器中被拆分:

union __attribute__ ((__packed__)) epsr_t 
    uint32_t storage;
    struct 
        unsigned reserved0    : 10;
        unsigned ICI_IT_2to7  :  6; // TOP HALF
        unsigned reserved1    :  8;
        unsigned T            :  1;
        unsigned ICI_IT_0to1  :  2; // BOTTOM HALF
        unsigned reserved2    :  5;
     bits;
;

在这种情况下,访问单个位 T 或任何 reserved 字段都可以正常工作,但要读取或写入 ICI_IT 需要更多类似的代码:

union epsr_t epsr;
// Reading:
uint8_t ici_it = (epsr.bits.ICI_IT_2to7 << 2) | epsr.bits.ICI_IT_0to1;
// Writing:
epsr.bits.ICI_IT_2to7 = ici_it >> 2;
epsr.bits.ICI_IT_0to1 = ici_it & 0x3;

在这一点上,我已经失去了位域抽象试图提供的简单性/便利性。我考虑了宏观解决方案:

#define GET_ICI_IT(_e)      ((_e.bits.ICI_IT_2to7 << 2) | _e.bits.ICI_IT_0to1)
#define SET_ICI_IT(_e, _i)  do \
    _e.bits.ICI_IT_2to7 = _i >> 2;\
    _e.bits.ICI_IT_0to1 = _i & 0x3;\
    while (0);

但我一般不喜欢这样的宏,我讨厌在阅读别人的代码时追逐它们,而且我绝不会给别人造成这样的痛苦。我希望有一个涉及结构/联合/what-have-you 的创意技巧来更优雅地隐藏该对象的拆分性质(理想情况下作为对象的简单成员)。

【问题讨论】:

坚持使用宏,真的。如果你想让你的代码花哨,假设你有 read_epsr / write_epsr 函数,让它们接受 struct 两个字段(T 和 ICC_IT)并在 funcs 中将其从 / 转换为 espr_t。 位域非常不可靠,尤其是以这种方式使用(指向另一个编译域或硬件中的某些东西)。硬件不是动态的,不会改变(不需要定义一次并使用很多)。无论如何,位域都会产生掩码和移位,只需直接或在宏中编码掩码和移位。 您是否有任何指针或参考说明它们何时真正成为问题?这是嵌入式领域的一个很常见的习语——当然不是我发明的...... Dan Saks 多年来在 www.embedded.com 上详细介绍了这个问题。这是他的一篇文章的link,但还有其他几篇文章可能会对您有所帮助。 只是一点评论。您无需命名结构中未使用的字段。删除 reserved0、reserved1 和 reserved2 名称绝对可以。这对于不需要设置这些字段的初始化程序有好处。 【参考方案1】:

您的编译器是否支持匿名联合

我发现它是一个优雅的解决方案,可以摆脱您的 .bits 部分。它不符合 C99,但大多数编译器都支持它。并且它成为了 C11 的标准。

另请参阅此问题:Anonymous union within struct not in c99?。

【讨论】:

摆脱 .bits 的解决方案会是什么样的? 回顾我的回答(经过这么多年),它没有多大意义:它没有提供解决方案。仍然需要位杂耍。【参考方案2】:

我不认为有任何“好”的方式,实际上我不会依赖位域...有时最好有一堆详尽的宏来完成你想做的所有事情,文档他们很好,然后依靠他们封装了你的问题......

#define ICI_IT_HI_SHIFT   14
#define ICI_IT_HI_MASK    0xfc
#define ICI_IT_LO_SHIFT   5
#define ICI_IT_LO_MASK    0x02

// Bits containing the ICI_IT value split in the 32-bit EPSR
#define ICI_IT_PACKED_MASK  ((ICI_IT_HI_MASK << ICI_IT_HI_SHIFT) |     \
                             (ICI_IT_LO_MASK << ICI_IT_LO_SHIFT))

// Packs a single 8-bit ICI_IT value x into a 32-bit EPSR e
#define PACK_ICI_IT(e,x)  ((e & ~ICI_IT_PACKED_MASK) |                 \
                           ((x & ICI_IT_HI_MASK) << ICI_IT_HI_SHIFT) | \
                           ((x & ICI_IT_LO_MASK) << ICI_IT_LO_SHIFT)))

// Unpacks a split 8-bit ICI_IT value from a 32-bit EPSR e
#define UNPACK_ICI_IT(e)  (((e >> ICI_IT_HI_SHIFT) & ICI_IT_HI_MASK) | \
                           ((e >> ICI_IT_LO_SHIFT) & ICI_IT_LO_MASK)))

请注意,为了便于阅读,我没有放入类型转换和普通宏的内容。是的,我在提到可读性时得到了讽刺......

【讨论】:

好奇为什么要避免使用位域?对于 32 位架构(当前上下文)上的单个字大小的结构,事情已经很好地定义了 据我了解,位域不受 C 标准的限制。编译器可以或多或少地以它选择的任何方式实现它们。如果您为单个平台使用单个编译器,也许没问题。但我不想依赖它们。 据我了解,至少有义务保持字段井然有序。到那时,一个早期的 assert(sizeof(union epsr_t) == 4) 就足以让偏执狂验证事情是否符合预期。在这一点上,我发现可读性大大提高。此外,当避免使用宏时,调试器和其他工具会获得不错的功能。 没关系。我不是专家,也许我的方式也有点固定。但是当我为一个定义非常明确的协议布置大块位时,我只是不相信位域,就像我相信冷硬位操作一样。用宏来做这一切会使代码更难维护,但是一旦我出于某种目的开发了一组宏,我会非常仔细地测试它们,然后再也不碰它们。 这很合理,只是无法扩展。从 100 个寄存器开始,其中大多数有 5~20 个组件,您会真正开始欣赏位域方法的可扩展性和可读性。无论如何,我认为我正在解决这个问题,只是想我会把它扔给全世界。【参考方案3】:

如果您非常不喜欢宏,请使用内联函数,但您拥有的宏解决方案很好。

【讨论】:

以上是关于具有非连续布局的 C 位域元素的主要内容,如果未能解决你的问题,请参考以下文章

c_cpp 具有不同值的连续元素的数组的计数

在 PyTorch 中,是啥让张量具有非连续内存?

熊猫:返回具有特定非连续列选择的新数据框[重复]

在python中访问列表或字符串的非连续元素

C中线性表和链表的区别

生成非连续组合