【问题标题】:Why this union's size is 2 with bitfields?为什么这个联合的大小是 2 位域?
【发布时间】:2012-08-15 05:20:48
【问题描述】:

我正在使用 char 占用一个字节的 windows 上的 turbo C。现在我的问题是下面的联合。

union a
{
 unsigned char c:2;
}b;
void main()
{
printf("%d",sizeof(b));  \\or even sizeof(union a)
}

这个程序将输出打印为 2,而 union 应该只占用 1 个字节。为什么会这样?

对于结构来说,给出 1 个字节很好,但这个联合工作不恰当。

还有一件事如何访问这些位域。

scanf("%d",&b.c);  //even scanf("%x",b.c);

不起作用,因为我们无法获得位地址。所以我们必须使用另一个变量,如下所示

int x;
scanf("%d",&x);
b.c=x;

我们不能避免吗?有没有其他办法???

【问题讨论】:

    标签: c structure bit-manipulation unions


    【解决方案1】:

    允许编译器向结构和联合添加填充,而我承认,当你能够获得一个单字节结构时,你的联合将四舍五入到两字节大小有点令人惊讶,这是完全允许的.

    回答您的第二个问题:不,这是无法避免的。位域是一种结构打包优化,要付出的性能和便利性损失是位域成员不可单独寻址。

    【讨论】:

    • 这对我来说并不奇怪。编译器选择使用 short 作为包含位域的最小大小类型是完全合理的。
    • +1 提到“位字段成员不可单独寻址”
    【解决方案2】:

    Turbo C is based on 8086 microprocessor which has two byte word boundary。原子读写通常与 CPU 的架构绑定,因此编译器会添加一些松弛字节来对齐您的数据结构。

    调用#pragma pack(1) 可能能够禁用它,但不确定它是否适用于 Turbo C。

    【讨论】:

    • 不,它不能在 turbo c 上工作。它仍然给出错误的 (2)
    • 我忘了说编译指示适用于结构,所以你必须用结构和联合包装你的字符。
    【解决方案3】:

    我不确定您在哪里找到联合必须恰好是最小大小的要求。对象必须至少与其成员一样大,但这只是一个下限。

    你不能取位域的地址;它的类型是什么?它不能是 int*。 scanf(%d) 会将 sizeof(int) * CHAR_BIT 位写入您传入的 int* 中。这是写入超过 2 位,但您没有那个空间。

    【讨论】:

      【解决方案4】:

      标准中有一段规定在结构的第一个成员之前不应有填充。但它并没有明确说明工会。可能会出现大小差异,因为它希望在 2 个字节边界处对齐联合,但由于它不能在结构的第一个成员之前填充,因此该结构将有一个字节对齐。另请注意,一个工会可能有更多不同类型的成员,这可能会扩大工会所需的对齐范围。编译器可能有理由给它们至少 2 个字节对齐,例如为了简化必须根据联合所需对齐处理的代码。

      无论如何,没有要求你的并集应该是一个字节。它只需要为其所有成员提供一席之地。

      以下是 C 标准对您的第二个问题的看法:

      The operand of the unary & operator shall be either a function designator or an lvalue that designates an object that is not a bit-field and is not declared with the register storage-class specifier.

      所以最好的办法是使用 int 的方式。你可以在代码周围加上大括号,这样临时变量就保持在本地:

      void func(void) { struct bits f; { int x; scanf("%d", &x); f.bitfield = x; } /* ... */ }
      

      【讨论】:

        【解决方案5】:

        答案中有很多错误信息,所以我会澄清一下。这可能是两个原因之一(我不熟悉编译器)。

        1. 位域存储单元为2。

        2. 强制对齐到字(2 字节)边界。

        我怀疑这是第一种情况,因为将位域存储单元作为声明的“基本”类型的大小是一种常见的扩展。在这种情况下,类型是 char,其大小始终为 1。

        [在标准中,您只能声明 int 或 unsigned int 类型的位域,并且位域分组的“存储单元”是固定的(通常与 int 大小相同)。即使是单个位域也会使用一个存储单元。]

        在第二种情况下,C 编译器通常实现#pragma pack 以允许控制对齐。我怀疑默认打包是 2,在这种情况下,将在联合末尾添加一个填充字节。避免这种情况的方法是使用:

        #pragma pack(1)
        

        您还应该在之后使用#pragma pack() 来设置回默认值(或者如果您的编译器支持,最好使用 push 和 pop 参数)。

        对于所有说您必须忍受编译器所做的事情的回复者,这与 C 的精神背道而驰。您应该能够使用位域来映射到任何大小或位顺序的情况下,您没有对其进行控制,例如文件格式或硬件映射。

        当然,这是高度不可移植的,因为不同的实现具有不同的字节顺序、位添加到位域存储单元的顺序(从顶部或底部)、存储单元大小、默认对齐方式等。

        关于你的第二个问题,我看不到问题所在,尽管我从不使用scanf,因为它有问题。

        【讨论】:

          【解决方案6】:

          除了“在结构或联合的末尾还可能有未命名的填充”这一事实之外,编译器还允许将位域放置在“任何大到足以容纳位域的可寻址存储单元”中. (这两个引用都来自 C90 标准 - 在 C99 标准中有类似但不同的措辞)。

          另请注意,标准规定“位域的类型应为 int、unsigned int 或 signed int 的限定或非限定版本”,因此在 char 类型中具有位域是非标准。

          因为位域的行为如此依赖于未指定的编译器实现细节(还有其他几个我没有提到的位域的不可移植问题)使用它们几乎总是一个坏主意。特别是,当您尝试对文件格式、网络协议或硬件寄存器中的位域进行建模时,它们不是一个好主意。


          更多信息来自another SO answer

          一般来说,您应该避免使用位域 并使用其他清单常量 (枚举或其他)具有显式位 掩蔽和转移以访问 字段中的“子字段”。

          这是位域应该使用的原因之一 避免 - 它们不是很便携 即使是相同的编译器之间 平台。来自 C99 标准 (C90也有类似的说法 标准):

          一个实现可以分配任何 足够大的可寻址存储单元 保存一个位域。如果空间足够 仍然是一个位域,立即 在 a 中跟随另一个位域 结构应装入 同一单元的相邻位。如果 剩余空间不足,无论是 放置不适合的位域 进入下一个单元或重叠 相邻单位是 实现定义。的顺序 单元内位域的分配 (高阶到低阶或低阶 到高阶)是 实现定义。对齐方式 可寻址存储单元的 未指定。

          你不能保证有点 字段将“跨越”一个 int 边界或 不是,你不能指定是否 位域从低端开始 int 或 int 的高端(这个 是独立于是否 处理器是大端或 小端)。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2014-12-29
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2023-04-02
            • 2019-02-03
            • 2015-02-13
            • 1970-01-01
            相关资源
            最近更新 更多