【问题标题】:Enforcing width of enum values within a struct in C99在 C99 中的结构中强制枚举值的宽度
【发布时间】:2014-08-15 08:27:50
【问题描述】:

我有一个打算使用 32 位存储的结构:

struct foo_t {
    color_t color : 10
    some_type_t some_field : 22;
} 

,其中color_t 是一个枚举,定义为

typedef enum {
    RED = 0,
    // other values...
    BLUE = 255
} color_t

请注意,color_t 值目前适合 8 位,尽管将来我们可能会添加更多值(因此我们为 color 保留了 10 位)

在 C99 中,我想知道是否有任何保证 color 的宽度会被编译器尊重。正如this question 中所讨论的,编译器可能会选择将color_t 表示为一个字符。此时,根据C99 spec,指定的宽度出现错误:

指定位域宽度的表达式应该是一个整数常量表达式,其非负值不超过在省略冒号和表达式的情况下指定类型的对象的宽度。

那么,我怎样才能强制 color 字段使用 10 位呢?请注意,如果编译器使用常规整数来表示 color_t,问题就会消失,但不能假设这种行为。

【问题讨论】:

  • 编译器可能会选择将 color_t 值表示为字符这听起来不对。
  • @this 你能详细说明一下吗?
  • @ouah 枚举是一个整数。
  • @this 当然不是,enum 常量是intenum 类型不需要是int
  • @user3120046 使用非枚举类型(例如,unsigned int)作为color 成员的类型将是更便携的解决方案恕我直言

标签: c struct enums c99 bit-fields


【解决方案1】:

您可以将所有数据组合在一个固定的 32 位 int (uint32_t) 中,在 stdint.h 中定义, 并通过位运算符访问值。

例子:

uint32_t data = (uint32_t)color<<22 | (uint32_t)some_field;
color=(color_t ) (data>>22);
some_field =(some_type_t ) (data & 0x3FFFFF);

【讨论】:

  • 那么如果不使用函数(封装按位操作),我将无法再设置/获取字段值如果可能,我想将foo_t 保留为结构并依赖编译器进行编写/读取必要的位。
  • @user3120046,你让这听起来像是一件坏事。我认为拥有隐藏位运算符的函数比使用带有位域的结构和/或在整个代码库中使用位运算符更好。
  • 我认为 color(myfoo) 与 myfoo.color 是拥有可靠且可移植的表示的代价。也许有一些方法可以让编译器采用固定宽度,但如果你没有找到它,这也可能很有用。我知道的另一种解决方案是采用适合的第一个类型(uint16_t 用于颜色,uint32_t 用于 somefiled),但这会破坏你试图获得的内存效率。你可以用我的方式为网络传输和文件保存保留效率,以及与我的其他方式的简单性,如果你结合这两个 aproches 并在需要时在两者之间转换它们
  • @RSahu:这不是一件可怕的事情 :-) 但如果你可以在语言中解决问题,那似乎是正确的解决方案:由于结构中允许使用位域,为什么要使用一个函数?如果您查看 Deduplicator 的回答,结构的设计保持不变,因此这是一个相对干净的更改。
【解决方案2】:

为您的enum 定义添加最后一个标签:

typedef enum {
    //...
    DUMMY_FORCE_WIDTH = 0xffffffffu, // INT_MAX,
} color_t;

这具有强制编译器/ABI 为您的enum 提供足够的增长空间的额外好处。

当然,前提是你的编译器允许enum's 作为位域类型。它不需要这样做,尽管它必须将其诊断为违反约束:

6.7.2.1 结构和联合说明符§5(约束)

位域的类型应为 _Bool 的合格或不合格版本,带符号 int、unsigned int 或其他一些实现定义的类型。它是 实现定义是否允许原子类型。

如果您想严格遵守,请定义有符号或无符号类型的位域,而不是您的特殊枚举类型。不过,这只能保证 16 位的长度。

【讨论】:

  • 我虽然是这样(我应该将它包含在问题中),但是如果我们假设color_t 是公共 API 的一部分,我们就会有这个“虚拟值”,这会令人困惑(这适用于我的问题)。您需要记录 FORCE_WIDTH 不能用作枚举的有效值等。
  • 其实这个名字根本就不应该出现在 API 文档中。如果FORCE_WIDTH 对您来说描述性不够,那么在前面加上一个假人再次说它不是一个有效值呢?无论如何,这种使用成员来强制大小的用法已经很成熟了。
  • 我接受了您的回答,因为它暗示了最小的实现更改,但是您是对的,将枚举类型和位域声明结合起来并不是标准的 C99 开始。我不太明白的是:C99 规范是否排除了 uint16_t 或 uint64_t 类型的位域?
  • 不,它说实现可能允许它们具有指定的语义,并且否则必须将它们诊断为错误。 (见上面的报价)
  • 所以使用 uint16_t 位域是不可移植的。有趣的。谢谢!
【解决方案3】:

严格来说,C 语言 (C99) 仅保证支持位域声明中的intsigned intunsigned int_Bool 类型,尽管明确允许特定实现支持其他类型作为实现-依赖行为。

换句话说,在这种情况下,我们正在处理特定于实现的行为,因为您使用 enum 类型声明了您的位字段。最好的方法是查阅您的编译器文档,但我希望它能够满足您对 10 位的请求,或者在所选存储单元无法容纳 10 位时生成诊断消息。

在我的实践中我通常使用以下方法

typedef enum color_t {
  RED = 0,
  // other values...
  BLUE,
  COLOR_COUNT
} color_t;

#define COLOR_BIT_WIDTH 8u

STATIC_ASSERT(COLOR_COUNT <= (1 << COLOR_BIT_WIDTH));
// Use your favorite implementation of `STATIC_ASSERT`

struct foo_t {
  unsigned color : COLOR_BIT_WIDTH;
  ...
};

请注意,此实现不会尝试为将来扩展颜色类型保留额外的位(您真的有理由这样做吗?)。它也不会尝试自动计算位域的宽度。相反,它只是捕捉宽度不再足够的情况,从而迫使代码维护人员注意到问题并手动修复它。

附:可以添加一个额外的简单STATIC_ASSERT 检查,以确保位域宽度不会过大,如果您希望它“恰到好处”。

【讨论】:

  • 如果可能的话,我希望解决方案符合标准,因此我不依赖特定的编译器。就目前而言,代码“可能”在某些编译器中工作,但它绝对(如您所指出的)不可移植。
  • 它可能会让维护者注意到存在问题,但它并不一定会突出显示被破坏的假设是硬编码的所有点。
  • @Deduplicator:想法是在需要位宽的任何地方使用COLOR_BIT_WIDTH 编写代码。这完全涵盖了位宽的问题。如果有人“忘记”它并依赖8,这确实是一个问题,但不是真正的“突出”问题。如果您在谈论其他类型的错误假设,则需要更具体。
猜你喜欢
  • 2017-08-19
  • 2019-02-21
  • 2019-02-07
  • 1970-01-01
  • 1970-01-01
  • 2020-08-30
  • 1970-01-01
相关资源
最近更新 更多