初始化位域。

12

当你写代码时

struct {
    unsigned a:3, b:2;
} x = {10, 11};

在 ANSI C(C89)中,x.b 是否保证为 3?我已经阅读了标准多次,但似乎没有找到确切的情况。

例如,“无法由结果无符号整数类型表示的结果会对大于结果无符号整数类型所能表示的最大值加一取模”,这是关于计算而不是初始化。而且,位域并不真正是一种类型。

此外,(当谈到 unsigned t:4 时)“包含范围在 [0,15] 的值”,但这并不意味着初始化程序必须被“对16取模”以映射到 [0,15]。

结构体初始化的描述非常详细,但我真的找不到确切的行为。(当然,编译器确实会这样做。IBM 文档说“当将超出范围的值赋给位域时,低位模式将被保留,并分配适当的位。”,但我想知道 ANSI C 是否标准化了这一点。


如果将位域视为实体,并按照标准的字面意思来解释,则“没有初始化程序应尝试为未包含在正在初始化的实体中的对象提供值”会禁止这种初始化,因为尝试为位域外的位提供值。我不确定这种解释是否正确。无论如何,我会避免这种情况,而是使用明确的{10&0x07,11&0x03}初始化程序。哦,我同意您关于标准在此问题上不够具体的看法。 - Sergey Kalinichenko
当然,没有人会在真正的代码中写这个。这是C89课程考试中的一个潜在问题。我认为这不是一个好问题,因为有人可能会给出不同的答案,但我们仍然无法证明他是错的。 - Veky
我同意,这对于一场考试来说是一个糟糕的问题,除非你正在测试一个旨在编写编译器的人(即使是这样,这仍然是一个非常棘手的问题)。 - Sergey Kalinichenko
只是为了确保没有误解……不,测试问题不是“x.b是否保证为3”,而只是“x.b会是什么”。他们不必知道标准的每个字母,我们只是想在有人挑剔并说它没有指定时保护自己。 :-) - Veky
2个回答

8

"ANSI C"/C89 已经过时25年了,因此我的回答引用了当前的C标准ISO 9899:2011, 也称为C11。


C标准中与位域相关的内容定义不清晰。通常情况下,你可能找不到明确说明位域行为的内容,但是它们的行为通常是隐含指定的。这就是为什么应该避免使用位域的原因。

然而,我认为这个具体的情况是明确定义的:它应该像任何其他整数初始化一样工作。

你提到的详细结构体初始化规则(6.7.9)展示了初始化器列表中的字面量11和变量b之间的关系。这并没有什么奇怪的。接下来应用的是“简单赋值”,也就是如果你写x.b = 11;会发生相同的事情。

在C中进行任何类型的赋值或初始化时,右操作数都将被转换为左操作数的类型。这由C11 6.5.16指定:

在简单赋值(=)中,右操作数的值被转换为赋值表达式的类型,并替换左操作数所指向的对象中的值。

在你的情况下,类型为int的字面量11将被转换为一个大小为unsigned int:2的位域。

因此,你要查找的规则应该在处理转换的章节中找到(C11 6.3)。适用的是你在问题中引述过的C11 6.3.1.3:

如果新类型是无符号的,则通过再加上可以在新类型中表示的最大值和一个来回多次进行的操作将该值转换为介于新类型范围内的值。

unsigned int:2的最大值为3。比最大值大1的值是3+1=4。编译器应该将这个值从11中不断地减去:

11 - (3+1) = 7    does not fit, subtract once more:
 7 - (3+1) = 3    does fit, store value 3

当然,这实际上就是将十进制数11的最不显著的2位存储在位字段中。


不错的回答,很有见地。你会相信来自任何来源的编译器可靠地实现C89标准吗?如果是旧编译器,你不会尝试一下吗?如果是新编译器,谁在乎呢? - david.pfx
2
请参见原问题的注释:这不是关于任何特定编译器的,而是关于一个名为“C89”的柏拉图实体。 :-) - Veky

3
关于“谈论计算,而不是初始化”,C89标准明确将赋值和转换规则应用于初始化。 它还说:
“位域被解释为由指定位数组成的整数类型。”
考虑到这些内容,虽然编译器警告显然是必要的,但似乎标准保证丢弃高阶位。

啊,是的...那句话我不知道怎么漏掉了。谢谢。 - Veky
然而,从字面上理解这显然是个谎言:sizeof(x.b)不是2/CHAR_BIT。 :-) - Veky
@Veky 我认为位域仍然是unsigned的一个子集。编译器会处理位,而不是你自己。如果你想深入到位级别,恐怕你必须自己做好记录。 - U. Windl

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