3

我正在 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;

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

4

6 回答 6

9

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

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

于 2008-11-14T12:24:10.553 回答
4

Turbo C 基于 8086 微处理器,具有两个字节的字边界。原子读写通常与 CPU 的体系结构绑定,因此编译器会添加一些松弛字节来对齐您的数据结构。

替代文字

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

于 2008-11-14T12:55:36.607 回答
1

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

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

于 2008-11-14T12:22:07.423 回答
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; } /* ... */ }
于 2008-11-14T12:26:55.900 回答
1

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

  1. 位域存储单元为 2。

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

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

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

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

#pragma pack(1)

您还应该使用#pragma pack()after 设置回默认值(如果您的编译器支持,甚至更好地使用 push 和 pop 参数)。

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

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

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

于 2010-02-01T11:55:43.960 回答
0

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

另请注意,该标准规定“位字段应具有一种类型,即 int、unsigned int 或 signed int 的合格或非合格版本”,因此在 char 类型中具有位字段是非标准的。

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


来自另一个 SO 答案的更多信息:

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

这是应该避免位域的一个原因 - 即使对于同一平台,它们在编译器之间也不是很便携。来自 C99 标准(C90 标准中有类似的措辞):

实现可以分配任何大到足以容纳位域的可寻址存储单元。如果有足够的空间剩余,紧跟在结构中另一个位域之后的位域将被打包到同一单元的相邻位中。如果剩余空间不足,则将不适合的位域放入下一个单元还是与相邻单元重叠是实现定义的。单元内位域的分配顺序(高位到低位或低位到高位)是实现定义的。未指定可寻址存储单元的对齐方式。

您无法保证位域是否会“跨越”一个 int 边界,并且您无法指定位域是从 int 的低端还是 int 的高端开始(这与处理器是否大端或小端)。

于 2008-11-14T17:49:40.197 回答