【问题标题】:Is it OK to force the size of a bool to 1 in a C struct?在 C 结构中将 bool 的大小强制为 1 是否可以?
【发布时间】:2019-04-15 14:29:14
【问题描述】:

我有一个包含 8 个布尔变量的 C 结构,我想将它们打包在 1 个字节中。

这是我定义结构的方式:

typedef struct
{
    bool a : 1;
    bool b : 1; 
    bool c : 1; 
    bool d : 1; 
    bool e : 1; 
    bool f : 1; 
    bool g : 1; 
    bool h : 1; 
} test_struct;

然后我想确定设置值是可以的:

test_struct my_struct = { 0 };
my_struct.e = true;

...并像这样进行测试:

if (my_struct.e)
    // do something

所有编译器都会提供预期的结果,即使用一位来存储布尔值吗?

【问题讨论】:

  • 我认为这取决于编译器,但你为什么不使用 uint8_t 和位掩码?
  • 如果bool_Bool 的类型定义,那么这应该可以正常工作:c11 标准草案 n1570:6.2.5 类型 2 声明为类型 _Bool 的对象足够大,可以存储值 0 和 1。 [...] 6 [...] 类型 _Bool 和对应于标准有符号整数类型的无符号整数类型是标准无符号整数类型。 [...]
  • 如果您担心不同的编译器“提供[ing]预期结果”,因为您需要交叉编译器兼容性,您不能使用位域,因为标准允许每个编译器实现它们不同,例如不同的尺寸和不同的排序。

标签: c struct boolean


【解决方案1】:

为了在内存中的未知位置存储许多标志而使用布尔位域实际上是唯一安全且可移植地使用位域。

所有编译器都会提供预期的结果,即使用一位来存储布尔值吗?

是的。 bool/_Bool 是位域的标准化类型,您发布的代码保证在不同编译器中表现相同。

但是,一旦您开始承担分配顺序,就会出现大量问题。例如*(uint8_t*)&my_struct 可能会导致任何结果。您不能随意假设各个布尔值在内存中的最终位置。

一种更安全、更简洁、更便携的方法是从不使用位域,而是使用位运算符:

#define a (1u << 0)
#define b (1u << 1)
#define c (1u << 2)
...

uint8_t test = 0;

然后:

  • 设置一个位:test |= a
  • 清除一个位:test &amp;= ~a
  • 检查位是否设置test &amp; a

对于较大的类型,这甚至可以完全移植到不同的 CPU 字节序。

【讨论】:

  • 注意:“对于较大的类型,这甚至可以完全移植到不同的 CPU 字节序中。” --> •清除单个位:testunsigned 宽时,test &amp;= ~a 是一个问题。也许test |= a; test ^= a; 或其他方法?
  • @chux 可移植性是指使用位移而不是位域。由于隐式提升,~ 运算符总体上很危险。在应用~ 之后,坚固的代码应始终转换回预期的类型。但那是另一回事了。
  • 嗯,我认为需要在 '~` 之前进行强制转换,以确保宽类型的扩展宽度。除了这个小角落,一个很好的答案。
【解决方案2】:

根据 C 2018 6.7.2.1 5,位字段的类型可能为 _Bool

位字段的类型应为_Boolsigned intunsigned int 或其他一些实现定义的类型的合格或非合格版本……

假设源包含&lt;stdbool.h&gt;bool就是_Bool,那么结构体定义就可以了。

请注意:

test_struct my_struct = { 0 };

将结构的所有命名成员初始化为零。这是初始化器规范的结果;它与位域是否打包成单个字节没有任何关系。

所有编译器都会提供预期的结果,即使用一位来存储布尔值吗?

是的,C 标准要求 C 实现打包位域,只要它们适合实现选择用于位域的可寻址存储单元,在 C 6.7.2.1 11 中:

…如果有足够的空间,结构中紧跟在另一个位域之后的位域应该被打包到同一单元的相邻位中…

因此,如果您有适合一个字节的连续一位位域(必须至少为八位),并且前面没有其他位域,则必须将它们打包到一个字节中。

【讨论】:

    【解决方案3】:

    这里的各个结构成员都引用一个位,大多数编译器会将它们打包在一起。但是,他们不是必须的;他们也不需要将结构的总大小设为 8 位;尽管您通常可以再次期望在您可能关心的大多数平台/编译器组合上都是这种情况。依赖于此的代码将是相当可移植的,但如果您正在寻找绝对保证,您不会在 C 标准中找到它。

    【讨论】:

    • 实际上,如果它们是同一类型,则需要将它们打包在一起,只要位域“存储单元”中有空间即可。反过来又是未指定的大小。
    • @Lundin 你在哪里找到的?我一直在 C99 草案标准中查找它,在 6.2.6.1 下它说“所有类型的表示都是未指定的,除非本子条款中另有说明。”随后only定义unsigned char的位域布局。它确实指定了n 位域的n 位表示,但这仅强制最小 大小。包装也没有提到。
    • 查看下面的声明 -> 结构说明符。特别是 C17 6.7.2.1/11。
    • "一个实现可以分配任何足够大的可寻址存储单元来保存一个位域。如果有足够的空间,结构中紧跟在另一个位域之后的位域应该被打包到相邻的位域中。相同的单元。如果剩余空间不足,是否将不适合的位域放入下一个单元或与相邻单元重叠是实现定义的。一个单元内的位域分配顺序(从高位到低位) -order 或低阶到高阶)是实现定义的。可寻址存储单元的对齐方式未指定。”
    猜你喜欢
    • 1970-01-01
    • 2012-02-01
    • 1970-01-01
    • 2020-10-09
    • 1970-01-01
    • 1970-01-01
    • 2021-06-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多