是否保证“置零”结构的填充位在C中置零?

时间:2018-10-06 23:28:29

标签: c structure padding memory-alignment bit-fields

article中的此声明使我感到尴尬:

  

C允许实现将填充插入结构(但不能插入数组),以确保所有字段都具有针对目标的有用对齐方式。 如果将结构归零,然后设置一些字段,填充位是否全部为零?根据调查结果,有36%的人会确定填充为零,而有29%的人会填充为零。不知道。取决于编译器(和优化级别),可能是,也可能不是。

这还不是很清楚,所以我转向标准。 §6.2.6.1中的ISO/IEC 9899状态:

  

当值存储在结构或联合类型的对象中(包括成员对象中)时,与任何填充字节对应的对象表示形式的字节采用未指定的值

也在§6.7.2.1中:

  

单位内位域的分配顺序(从高位到低位或从低位到高位)是实现定义的。未指定可寻址存储单元的对齐方式。

我只记得最近我实现了某种黑客手段,我使用了位字段拥有的未声明字节部分。就像这样:

/* This struct is always allocated on the heap and is zeroed. */
struct some_struct {
  /* initial part ... */
  enum {
    ONE,
    TWO,
    THREE,
    FOUR,
  } some_enum:8;
  unsigned char flag:1;
  unsigned char another_flag:1;
  unsigned int size_of_smth;
  /* ... remaining part */
};

该结构不是我可以使用的,因此我无法更改它,但是我迫切需要通过它传递一些信息。所以我计算了对应字节的地址,如:

unsigned char *ptr = &some->size_of_smth - 1;
*ptr |= 0xC0; /* set flags */

然后,我以相同的方式检查标志。

我还要提到目标编译器和平台已定义,因此这不是跨平台的。但是,当前的问题仍然存在:

  1. 我可以依靠这样一个事实:在memset / kzalloc /什么使用之后,struct(在堆中)的填充位仍会归零吗? (This post并未就标准和进一步使用struct的保护措施公开此主题)。那么像= {0}这样的堆栈上的结构归零呢?

  2. 如果是,这是否意味着我可以安全地使用C的位字段的“未命名” /“未声明”部分来传输我所需的某些信息(不同的平台,编译器,..)? (如果我确定没有人疯狂地尝试在此字节中存储任何内容)。

3 个答案:

答案 0 :(得分:3)

第一个问题的简短答案是“否”。

虽然对memset()的适当调用(例如memset(&some_struct_instance, 0, sizeof(some_struct))会将结构中的所有字节都设置为零,但是在“ some_struct_instance的“某些用途”之后,该更改不需要是持久的) ,例如在其中设置任何成员。

因此,例如,不能保证some_struct_instance.some_enum = THREE(即将值存储到成员中)将使some_struct_instance中的任何填充位保持不变。该标准的唯一要求是该结构的其他成员的值不受影响。但是,编译器可以(在发出的目标代码或机器指令中)使用一组按位操作来实现分配,并允许其采取捷径的方式不会遗漏填充位(例如,不发出将否则请确保填充位不受影响。

更糟糕的是,像some_struct_instance = some_other_struct_instance这样的简单赋值(按照定义,就是将值存储到some_struct_instance中)无法保证填充位的值。不保证some_struct_instance中的填充位将被设置为与some_other_struct_instance中的填充位相同的按位值,也不保证some_struct_instance中的填充位将保持不变。这是因为允许编译器以其认为最“有效”的方式实现分配(例如,逐字复制内存,按成员分配的某些集合等),但是-由于未指定分配后的填充位的值-不需要确保填充位不变。

如果您很幸运,并且对填充位有所了解的话就可以满足您的目的,这并不是因为C标准中有任何支持。这是由于编译器供应商的良好风采(例如选择发出一组确保填充位不变的机器指令)。而且,实际上,不能保证编译器供应商会保持相同的方式运行-例如,当更新编译器,选择不同的优化设置或执行其他操作时,依赖于这种操作的代码可能会中断。 / p>

由于第一个问题的答案为“否”,因此无需回答第二个问题。但是,从哲学上讲,如果 you 试图将数据存储在结构的填充位中,则可以合理地断言其他人-可能是疯狂的-可能会尝试这样做同样的事情,只是使用一种弄乱您要传递的数据的方法。

答案 1 :(得分:1)

从标准规范的第一句话开始:

  

C允许实现将填充插入结构(但不能插入数组),以确保所有字段都具有有用的对齐方式...

这些词的意思是,为了优化(可能是为了提高速度,还可能是为了避免对数据/地址总线的体系结构限制),编译器可以利用隐藏的,未使用的位或字节。未使用,因为它们被禁止或昂贵的解决。

这还意味着从编程角度看这些字节或位应该是不可见的,并且尝试访问那些隐藏数据应该被视为编程错误。

关于那些添加的数据,该标准说它们的内容是“未指定”的,实际上没有更好的方法来说明实现可以对它们进行处理。想想那些位域声明,您可以在其中声明任何位宽的整数:没有普通的硬件将允许以小于8位的块的形式从内存中进行读取/写入,因此CPU将始终至少读取或写入8位(有时,甚至更多)。为什么编译器(实现)应该对其他那些他不关心的位进行有益的处理呢?这是没有意义的:程序员没有给某个内存地址命名,但是他想操纵它?

字段之间的填充字节与以前几乎相同:添加的字节是必需的,但是程序员对它们不感兴趣-并且他以后不应该改变主意!

当然,可以研究实现并得出一些结论,例如“填充字节将始终为零”或类似的东西。这是有风险的(您确定它们总是始终为零吗?),但更重要的是,它完全没有用:如果您在结构中需要更多数据,只需声明它们!而且,即使将源代码移植到不同的平台或实现中,也永远不会有问题。

答案 2 :(得分:0)

可以合理地预期标准中列出的内容。您正在寻找特定体系结构的进一步保证。就个人而言,如果我能找到有关该特定体系结构的详细文档记录,那将使我放心;如果没有,我会保持谨慎。

什么构成“谨慎”取决于我要有多自信。例如,构建一个详细的测试集并在目标体系结构上定期运行它会给我合理的信心,但这全都与您要承担多少风险有关。如果真的非常重要,请遵循他们为您提供保证的标准;如果不是这样,请测试一下,看看您是否对自己的需求有足够的信心。