【问题标题】:Do unnamed bit-fields have well-defined semantics?未命名的位域是否具有明确定义的语义?
【发布时间】:2013-09-10 03:47:04
【问题描述】:

以下代码是否保证正常终止并成功?

#include <assert.h>

struct foo_s {
    union {
        struct {
            unsigned a : 10;
            unsigned   : 6;
        };
        struct {
            unsigned   : 10;
            unsigned b : 6;
        };
        struct {
            unsigned   : 10;
            unsigned c : 6;
        };
    };
};

int main () {
    struct foo_s f;
    f.a = 0;
    f.b = 1;
    assert(f.a == 0);
    return 0;
}

在回答不同的question 时,提出了分配给还包含未命名位字段的结构中的命名位字段的可能性,这可能会导致将任意数据写入这些位。 C.11 §6.7.2.1 ¶12 状态:

没有声明符但只有冒号和宽度的位域声明表示 未命名的位域。

我对此的理解是,未命名的位域只是一个常规位域,唯一的区别是这些位中的值不能直接通过名称获得。是否允许使用“as-if”逻辑推断实现并在这些位中分配任意数据?

【问题讨论】:

  • 几乎所有与位字段有关的“实现定义”行为都有很多。为什么你需要冒险?为什么不只拥有一个不包含未命名位字段的结构呢?当然,分配给联合的一个部分并从另一个部分读取也是不明确的(并且对 .a.b 的引用是对联合的不同(匿名)成员的引用。
  • Unnamed 位域在运行时的内容是不可预测的,所以 NO 不能保证正常且成功地终止
  • @JonathanLeffler:使用位域的正常原因都是基于在低级事物上定义更高级别的接口(例如协议头中的字段)。这不仅在代码中有用,而且在调试器中检查数据时也很有用。但是,我想抛开这个问题,专注于语义是否定义明确。
  • @Dayalrai:如果这些位从未初始化,我同意。但是如果这些位被初始化(我给出了一种方式,但另一种方式是memset()),这些位是否仍然不可预测?
  • @jxh 我看到了泥潭。 C.11 §6.7.2.1 ¶12 note 126 说“一个未命名的位域结构成员对于 padding 以符合外部强加的布局很有用。”如果实现认为这是真正的 padding,那么 f.b = 1 可能会搞砸 f.a。 OTH “结构对象中可能有未命名的填充,但不在其开头。”所以看起来你的断言“未命名的位域只是一个常规的位域”具有可信度。 (我没有使用过,也没有经常看到未命名的位字段,所以没有经验。我给他们起假名。)

标签: c language-lawyer c11


【解决方案1】:

是的,我认为实现可以将任意位写入未命名的位字段。我认为脚注 126 只是说明了为什么引入宽度大于 0 的未命名位域的意图:

未命名的位域结构成员可用于填充 符合外部强加的布局。

所以基本上未命名的位域与填充位具有相同的语义。你不能依赖他们的内容。

允许实现在写入相邻的命名位字段a 时基本上忽略未命名的位字段,从而大大简化了该字段a 的处理。不必读取未命名字段的当前值,并且可以一次原子地完成写入。就像结构中可能包含的填充位一样。

【讨论】:

  • 该标准似乎非常具体地说明了它在何处使用术语填充位,并且它从不在位域的上下文中。脚注读起来更像是对用例的描述,而不是强加语义。通常,“外部强加布局”具有“保留以供将来使用”位,并且保留位通常应该始终为 0(某些硬件会检查线路上的这些位),因此未命名的位字段都符合布局以及防止程序员意外使用这些位。如果编译器在那里设置随机值,那么一切都会顺利。
  • @jxh,我认为您对“外部强加布局”的解释更多。它只是谈论 paddidng,而不是关于保留位以供将来使用。如果应用程序需要使用这些位,并且只是为了确保将它们设置为0,它应该只命名位域。
  • 我的主要观点是,术语未命名的位域在标准中出现了两次(定义术语的地方,脚注中的使用说明)。它永远不会出现在术语“填充位”附近,并且“填充位”永远不会应用于位字段。 OTOH,详细描述了“位域”,并且鉴于术语“未命名位域”的定义方式,似乎“位域”的所有描述都同样适用于“未命名位域”。跨度>
  • @jxh:该术语本身出现在另一个示例中(分成两行),并且在 6.7.9“初始化”中特别提到了未命名的结构成员。它明确指出未命名的成员不参与初始化并且具有不确定的值。附件 J.2 还提到了对未命名成员的访问,这使得行为未定义。
  • 所以,我在用comp.std.c 提出这个问题后得到的是,尽管标准中的语言确实未定义,但在这种情况下,实际行为才是最重要的,因为“兼容”编译器这与程序员在协议缓冲区这样基本的东西上所期望的行为大相径庭,这会给编译器供应商带来厄运。
猜你喜欢
  • 1970-01-01
  • 2020-09-16
  • 1970-01-01
  • 2014-02-07
  • 1970-01-01
  • 1970-01-01
  • 2022-01-09
  • 2019-04-30
  • 1970-01-01
相关资源
最近更新 更多