在位域(Bit Fields)的情况下,使用无符号字符型(unsigned char)还是无符号整型(unsigned int)更好?为什么?

7

我想了解以下结构声明。哪一个更适合用于内存分配,为什么?在unsigned char和unsigned int的情况下,关于填充有什么需要注意的吗?

struct data{
 unsigned char a:3;
 unsigned char b:4;
}; 

并且
struct data{
 unsigned int a:3;
 unsigned int b:4;
};

2
更好的是什么?时间,空间,温度,心情,还是其他? - Bryan Olivier
请参见https://dev59.com/RFHTa4cB1Zd3GeqPP0G4。 - devnull
@BryanOlivier 更适合内存分配。 - Chinna
2
@Chinna,内存分配与此无关,因为您无论如何都指定了字段占用的位数。 - Jens Gustedt
4个回答

8
位域应该声明为类型为signed int, unsigned int。其他类型可能支持,也可能不支持。
来自Atmel 在C标准中,只有“unsigned (int)”和“int”是位域成员可接受的数据类型。一些编译器允许使用“unsigned char”......

C语言规定“_Bool,signed int,unsigned int”均可接受,不仅限于本回答中提到的两者。Atmel网站提供的信息已经过时。 - chux - Reinstate Monica

5
在c99标准(§6.7.2.1 #4)中规定:

位域(bit-field)必须有一个类型,该类型可以是_Bool、signed int、unsigned int或其他实现定义的类型的限定或非限定版本。

如果实际使用的类型指示符为int或typedef名称定义为int,则是否将位域设为signed或unsigned是实现定义的。
在结构体或联合体的末尾可能有未命名的填充(§6.7.2.1 #15)。
实现可以分配任何足够大的可寻址存储单元来容纳位域。
进一步(§6.7.2.1 #11):

带有没有声明符但只有一个冒号和一个宽度的位域声明表示一个未命名的位域。作为一个特例,宽度为0的位域结构成员表示不再将任何其他位域打包到前一个位域(如果有的话)所在的单元中。

未命名的位域结构成员用于填充以符合外部强制布局要求。

2
正如Amogh和PHIfounder坚持的那样,唯一完全可移植的类型是_Bool、signed int和unsigned int。然而,许多编译器允许其他整数类型来处理位域的打包。实际上,位域通常用于表示设备寄存器,其中通常每个位或一组位都有自己的含义。比特位的打包由C标准的6.7.2.1 ad 11指定。

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

许多编译器已经采用了“可寻址存储单元”的约定,该单元的类型与源中指定的类型相同。例如,gcc编译器不允许使用unsigned char类型的9位位域,但允许使用unsigned int类型的位域。在您的示例中,对于Pentium使用unsigned char的结构体将是1个字节大小,而使用unsigned int的结构体将是4个字节。许多编译器还采用了这样的约定:如果位域不适合,则它将不会与下一个单元重叠。但是,可以使用标准的6.7.2.1 ad 12所规定的0宽度位域来强制执行此操作。

没有声明符但只有冒号和宽度的位域声明表示未命名的位域。作为特殊情况,宽度为0的位域结构成员表示不再将其他位域打包到先前放置的单元中。

如果将位域与非位域混合使用,则6.7.2.1 ad 15规定位域和非位域的可寻址单元将具有不同的地址。

在结构对象内,非位域成员和位域所在的单元按其声明顺序递增地具有地址。经过适当转换的指向结构对象的指针指向其初始成员(或者如果该成员是位域,则指向其所在的单元),反之亦然。结构对象内可能存在未命名的填充,但不能在其开头处存在。

一些体系结构的应用程序二进制接口(ABI)强制执行实现定义的选择,以确保该体系结构的不同编译器之间的互操作性。

1
哪个更好使用,unsigned char 还是 unsigned int?为什么?
无符号整型。
以下内容不具备可移植性,应将其考虑排除在外。
// Only portable bit field types are _Bool, signed int, unsigned int
// This is not portable.
struct data{
  unsigned char a:3;
  unsigned char b:4;
};

那留给OP的选择。
struct data{
  unsigned int a:3;
  unsigned int b:4;
};

如果内存使用最小化是主要目标,则不要使用位域。在这种情况下,使用最小的整数类型:unsigned char。添加函数或定义以获取和设置。
void data_a_set(unsigned char *data, unsigned a) {
  *data = *data & ~7u | a & 7u;
}
unsigned data_a_get(unsigned char data) {
  return data & 7u;
}

// or for a more generic approach
#define B_BITS_PRIOR 3 /* sum of all previous bit widths */
#define B_BITS 4
#define MASK(w,p) (((1u << (w)) - 1) << (p))

void data_b_set(unsigned char *data, unsigned b) {
  *data &= ~MASK(B_BITS, B_BITS_PRIOR);
  *data |=  MASK(B_BITS, B_BITS_PRIOR) & (b << B_BITS_PRIOR);
}
unsigned data_b_get(unsigned char data) {
  return (data & MASK(B_BITS, B_BITS_PRIOR) >> B_BITS_PRIOR;
}

对于比unsigned更宽的类型,需要进行小的更改以确保更宽的移位操作。
如果需要唯一类型,则可以将unsigned char包装在struct中。
struct data {
  unsigned char ab;
}

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