为什么这个联合的大小是2的位域?

时间:2008-11-14 12:08:20

标签: c structure bit-manipulation unions

我在Windows上使用turbo C,其中char占用一个字节。现在我的问题在于下面的联合。

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

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

对于struct,它可以很好地给出1个字节,但是这个联合工作不正常。

还有一个如何访问这些位字段。

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

无效,因为我们无法获得位数。所以我们必须使用另一个变量,如下所示

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

我们不能避免吗?有没有别的办法???

6 个答案:

答案 0 :(得分:9)

允许编译器向结构和联合添加填充,而且,我承认,当你能够获得一个字节结构时,你的联盟将两个字节的大小简化为一个有点令人惊讶的是它是完全允许的

回答你的第二个问题:不,这是不可避免的。位字段是结构打包优化,并且要支付的性能和便利性代价是位字段成员不能单独寻址。

答案 1 :(得分:4)

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

alt text

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

答案 2 :(得分:1)

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

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

答案 3 :(得分:1)

标准中有一个段落声明在结构的第一个成员之前不应有填充。但它没有明确说明工会。大小的差异可能是因为它想要以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; } /* ... */ }

答案 4 :(得分:1)

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

  1. 位域存储单元为2.

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

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

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

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

    #pragma pack(1)
    

    之后你也应该使用#pragma pack()设置回默认值(如果你的编译器支持,甚至可以更好地使用push和pop参数)。

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

    当然这是非常不便携的,因为不同的实现具有不同的字节顺序,将位添加到位域存储单元(从顶部或底部),存储单元大小,默认对齐等的命令。

    关于你的第二个问题,我看不出问题,但我从不使用scanf因为它有问题。

答案 5 :(得分:0)

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

另请注意,标准表示“位字段的类型必须是int,unsigned int或signed int的限定或非限定版本”,因此在char类型中使用bit-field是非标准。

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


来自another SO answer的更多信息:

  

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

     

这就是位域应该存在的一个原因   要避免 - 它们不是很便携   编译器之间甚至是相同的   平台。来自C99标准   (C90中有类似的措辞   标):

     
    

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

  
     

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