为什么这个联合体的大小是 2(带位域)?
-
08-07-2019 - |
题
我正在 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;
我们不能避免吗?还有其他办法吗???
解决方案
Turbo C基于8086微处理器,它有两个字节的字边界。原子读取和写入通常绑定到CPU的体系结构,因此编译器会添加一些松弛字节来对齐数据结构。
调用#pragma pack(1)
可能会禁用它,但不确定它是否适用于Turbo C.
其他提示
允许编译器向结构和联合添加填充,而且,我承认,当你能够获得一个字节结构时,你的联盟将两个字节的大小简化为一个有点令人惊讶的是它是完全允许的
回答你的第二个问题:不,这是不可避免的。位字段是结构打包优化,并且要支付的性能和便利性代价是位字段成员不能单独寻址。
我不确定你在哪里找到联合必须精确到最小尺寸的要求。对象必须至少与其成员一样大,但这只是一个下限。
您无法获取位域的地址;它的类型是什么?它不能是int *。 scanf(%d)会将sizeof(int)* CHAR_BIT位写入您传入的int *。这是写入超过2位,但您没有该空格。
标准中有一段规定结构体的第一个成员之前不应有填充。但它并没有明确提及工会。大小差异可能是因为它想要在 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; } /* ... */ }
答案中有很多错误信息,所以我会澄清。这可能是由于两个原因之一(我不熟悉编译器)。
-
位域存储单元为2.
-
对齐被强制为字(2字节)边界。
醇>
我怀疑这是第一种情况,因为它是将位域存储单元作为声明的<!> quot; base <!>的大小的常见扩展。类型。在这种情况下,类型是char,其大小始终为1。
[在标准中,您只能声明int或unsigned int类型的位域和<!> quot;存储单元<!>;其中位域分组是固定的(通常与int的大小相同)。即使是单个位位域也将使用一个存储单元。]
在第二种情况下,C编译器通常实现#pragma pack
以允许控制对齐。我怀疑默认打包是2,在这种情况下,将在联合的末尾添加填充字节。避免这种情况的方法是使用:
#pragma pack(1)
您还应该使用#pragma pack()
设置回默认值(如果您的编译器支持,甚至可以更好地使用push和pop参数)。
对于那些说你必须忍受编译器所做的事情的所有回复者来说,这与C的精神背道而驰。你应该能够在你没有的情况下使用位域映射到任何大小或位顺序控制它,例如文件格式或硬件映射。
当然这是非常不便携的,因为不同的实现具有不同的字节顺序,将位添加到位域存储单元(从顶部或底部),存储单元大小,默认对齐等的命令。
至于你的第二个问题,我看不出问题,虽然我从不使用scanf
因为它有问题。
除了<!>“之外,在结构或union <!>”的末尾也可能有未命名的填充,允许编译器在<!>中添加一个位域。可寻址存储单元,大到足以容纳位字段<!>。 (这两个引用均来自C90标准 - C99标准中有相似但不同的措辞。)
另请注意,标准表示<!>“位字段的类型应为int,unsigned int或signed int <!>”的限定或非限定版本,因此具有一定的位 - char类型中的字段是非标准的。
因为位域的行为如此依赖于未指定的编译器实现细节(还有其他一些我未提及的位域非易失性问题)使用它们几乎总是一个坏主意。特别是,当您尝试对文件格式,网络协议或硬件寄存器中的位字段建模时,它们是个坏主意。
来自另一个SO答案:
一般来说,你应该避免使用位域 并使用其他清单常量 (枚举或其他)带有显式位 掩盖和转移访问 字段中的“子字段”。
这就是位域应该存在的一个原因 要避免 - 它们不是很便携 编译器之间甚至是相同的 平台。来自C99标准 (C90中有类似的措辞 标):
实施可以分配任何 可寻址的存储单元足够大 持有一个位域。如果有足够的空间 仍然是一个立即的比特场 跟随a中的另一个位字段 结构应包装成 相同单元的相邻位。如果 空间不足,无论是否 放置不适合的位字段 进入下一个单位或重叠 相邻单位是 实现定义。的顺序 单位内的位字段分配 (高阶到低阶或低阶 到高阶)是 实现定义。对齐 可寻址存储单元的 未指定的。
你无法保证是否有点 字段将“跨越”一个int边界或 没有,你不能指定是否 bitfield从低端开始 int或int的高端(这个 独立于是否独立 处理器是big-endian或 小端)。