是否保证 "zeroed" 结构的填充位将在 C 中归零?
Is it guaranteed that the padding bits of "zeroed" structure will be zeroed in C?
article中的这句话让我很尴尬:
C permits an implementation to insert padding into structures (but not into arrays) to ensure that all fields have a useful alignment for the target. If you zero a structure and then set some of the fields, will the padding bits all be zero? According to the results of the survey, 36 percent were sure that they would be, and 29 percent didn't know. Depending on the compiler (and optimization level), it may or may not be.
不是很清楚,所以我转向了标准。 §6.2.6.1 中的 ISO/IEC 9899 指出:
When a value is stored in an object of structure or union type, including in a member object, the bytes of the object representation that correspond to any padding bytes take unspecified values.
也在 §6.7.2.1:
The order of allocation of bit-fields within a unit (high-order to low-order or low-order to high-order) is implementation-defined. The alignment of the addressable storage unit is unspecified.
我只记得我最近实施了某种 hack,其中我使用了位域拥有的字节的未声明部分。它是这样的:
/* 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 */
后来我用同样的方法检查了标志。
另外我应该提到目标编译器和平台已经定义,所以这不是跨平台的事情。然而,目前的问题仍然存在:
我可以相信结构的填充位(在堆中)在 memset
/kzalloc
/whatever 之后以及在一些后续使用之后仍将归零吗? (This post 未在进一步使用 struct 的标准和保障方面公开主题)。那么像 = {0}
?
这样在堆栈上归零的结构呢?
如果是,是否意味着我可以安全地使用 "unnamed"/"not declared" 部分位域来为我的目的在任何地方传输一些信息(不同的平台,编译器, ..) 在 C 中? (如果我确定没有疯子试图在这个字节中存储任何东西)。
从期望标准中列出的内容得到正确实施开始是合理的。您正在寻找对特定架构的进一步保证。就个人而言,如果我能找到有关该特定架构的文档详细信息,我会感到放心;如果没有,我会小心。
"cautious" 的构成取决于我需要多自信。例如,在我的目标架构上定期构建一个详细的测试集和 运行 这会让我有一定程度的信心,但这完全取决于你愿意承担多少风险。如果它真的非常重要,请坚持他们的标准向您保证的内容;如果不是这样,请测试一下,看看您是否对所需的东西有足够的信心。
第一个问题的简短答案是 "no"。
虽然 memset()
的适当调用(例如 memset(&some_struct_instance, 0, sizeof(some_struct))
会将结构中的所有字节设置为零,但在 "some use" 之后不需要持续更改 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
中的填充位将保持不变.这是因为允许编译器以它认为最 "efficient" 的任何方式实现赋值(例如,逐字复制内存,一些成员赋值集,或其他)但是 - 因为赋值后填充位的值未指定 - 不需要确保填充位不变。
如果你幸运的话,摆弄填充位可以达到你的目的,那不会是因为 C 标准中的任何支持。这将是因为编译器供应商的好意(例如,选择发出一组机器指令以确保填充位不被更改)。而且,实际上,不能保证编译器供应商会继续以相同的方式做事——例如,当编译器更新、选择不同的优化设置或其他任何原因时,依赖于这种东西的代码可能会中断。
由于您的第一个问题的答案是"no",因此无需回答您的第二个问题。然而,从哲学上讲,如果 you 正在尝试将数据存储在结构的填充位中,则可以合理地断言 someone else - 疯狂与否- 可能会尝试做同样的事情,但使用的方法会弄乱您试图传递的数据。
来自标准规范的第一句话:
C permits an implementation to insert padding into structures (but not into arrays) to ensure that all fields have a useful alignment ...
这些词的意思是,为了优化(可能是为了速度而优化,同时也是为了避免架构对 data/address 总线的限制),编译器可以利用隐藏的、未使用的位或字节。未使用,因为它们将被禁止或解决成本高昂。
这也意味着从编程的角度来看,这些字节或位不应该是可见的,并且尝试访问那些隐藏的数据应该被视为编程错误。
关于那些添加的数据,标准说它们的内容是 "unspecified",而且确实没有更好的方式来说明实现可以用它们做什么。想想那些位域声明,您可以在其中声明具有任何位宽的整数:没有正常的硬件允许 read/write 从小于 8 位的块中的内存中读取,因此 CPU 将始终读取或写入至少8 位(有时甚至更多)。为什么编译器(一个实现)应该负责对程序员指定他不关心的那些其他位做一些有用的事情?无意义:程序员没有给某个内存地址起名字,然后他想操作它?
字段之间的填充字节与以前几乎相同:那些添加的字节是必需的,但程序员对它们不感兴趣 - 他以后不应该改变主意!
当然,可以研究一种实现并得出一些结论,例如 "padding bytes will always be zeroed" 或类似的东西。这是有风险的(你确定它们会一直归零吗?)但是,更重要的是,它完全没有用:如果你需要结构中的更多数据,只需声明它们!而且你不会有任何问题,永远不会,甚至将源代码移植到不同的平台或实现。
article中的这句话让我很尴尬:
C permits an implementation to insert padding into structures (but not into arrays) to ensure that all fields have a useful alignment for the target. If you zero a structure and then set some of the fields, will the padding bits all be zero? According to the results of the survey, 36 percent were sure that they would be, and 29 percent didn't know. Depending on the compiler (and optimization level), it may or may not be.
不是很清楚,所以我转向了标准。 §6.2.6.1 中的 ISO/IEC 9899 指出:
When a value is stored in an object of structure or union type, including in a member object, the bytes of the object representation that correspond to any padding bytes take unspecified values.
也在 §6.7.2.1:
The order of allocation of bit-fields within a unit (high-order to low-order or low-order to high-order) is implementation-defined. The alignment of the addressable storage unit is unspecified.
我只记得我最近实施了某种 hack,其中我使用了位域拥有的字节的未声明部分。它是这样的:
/* 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 */
后来我用同样的方法检查了标志。
另外我应该提到目标编译器和平台已经定义,所以这不是跨平台的事情。然而,目前的问题仍然存在:
我可以相信结构的填充位(在堆中)在
memset
/kzalloc
/whatever 之后以及在一些后续使用之后仍将归零吗? (This post 未在进一步使用 struct 的标准和保障方面公开主题)。那么像= {0}
? 这样在堆栈上归零的结构呢?
如果是,是否意味着我可以安全地使用 "unnamed"/"not declared" 部分位域来为我的目的在任何地方传输一些信息(不同的平台,编译器, ..) 在 C 中? (如果我确定没有疯子试图在这个字节中存储任何东西)。
从期望标准中列出的内容得到正确实施开始是合理的。您正在寻找对特定架构的进一步保证。就个人而言,如果我能找到有关该特定架构的文档详细信息,我会感到放心;如果没有,我会小心。
"cautious" 的构成取决于我需要多自信。例如,在我的目标架构上定期构建一个详细的测试集和 运行 这会让我有一定程度的信心,但这完全取决于你愿意承担多少风险。如果它真的非常重要,请坚持他们的标准向您保证的内容;如果不是这样,请测试一下,看看您是否对所需的东西有足够的信心。
第一个问题的简短答案是 "no"。
虽然 memset()
的适当调用(例如 memset(&some_struct_instance, 0, sizeof(some_struct))
会将结构中的所有字节设置为零,但在 "some use" 之后不需要持续更改 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
中的填充位将保持不变.这是因为允许编译器以它认为最 "efficient" 的任何方式实现赋值(例如,逐字复制内存,一些成员赋值集,或其他)但是 - 因为赋值后填充位的值未指定 - 不需要确保填充位不变。
如果你幸运的话,摆弄填充位可以达到你的目的,那不会是因为 C 标准中的任何支持。这将是因为编译器供应商的好意(例如,选择发出一组机器指令以确保填充位不被更改)。而且,实际上,不能保证编译器供应商会继续以相同的方式做事——例如,当编译器更新、选择不同的优化设置或其他任何原因时,依赖于这种东西的代码可能会中断。
由于您的第一个问题的答案是"no",因此无需回答您的第二个问题。然而,从哲学上讲,如果 you 正在尝试将数据存储在结构的填充位中,则可以合理地断言 someone else - 疯狂与否- 可能会尝试做同样的事情,但使用的方法会弄乱您试图传递的数据。
来自标准规范的第一句话:
C permits an implementation to insert padding into structures (but not into arrays) to ensure that all fields have a useful alignment ...
这些词的意思是,为了优化(可能是为了速度而优化,同时也是为了避免架构对 data/address 总线的限制),编译器可以利用隐藏的、未使用的位或字节。未使用,因为它们将被禁止或解决成本高昂。
这也意味着从编程的角度来看,这些字节或位不应该是可见的,并且尝试访问那些隐藏的数据应该被视为编程错误。
关于那些添加的数据,标准说它们的内容是 "unspecified",而且确实没有更好的方式来说明实现可以用它们做什么。想想那些位域声明,您可以在其中声明具有任何位宽的整数:没有正常的硬件允许 read/write 从小于 8 位的块中的内存中读取,因此 CPU 将始终读取或写入至少8 位(有时甚至更多)。为什么编译器(一个实现)应该负责对程序员指定他不关心的那些其他位做一些有用的事情?无意义:程序员没有给某个内存地址起名字,然后他想操作它?
字段之间的填充字节与以前几乎相同:那些添加的字节是必需的,但程序员对它们不感兴趣 - 他以后不应该改变主意!
当然,可以研究一种实现并得出一些结论,例如 "padding bytes will always be zeroed" 或类似的东西。这是有风险的(你确定它们会一直归零吗?)但是,更重要的是,它完全没有用:如果你需要结构中的更多数据,只需声明它们!而且你不会有任何问题,永远不会,甚至将源代码移植到不同的平台或实现。