这些问题适用于
C99 (TC3)
和C11 (TC1)
。#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
int main(void) {
struct s {
size_t len;
char pad;
int array[];
};
struct s *s = malloc(sizeof *s + sizeof *s->array);
printf("sizeof *s: %zu\n", sizeof *s);
printf("offsetof(struct s, array): %zu\n", offsetof(struct s, array));
s->array[0] = 0;
s->len = 1;
printf("%d\n", s->array[0]);
free(s);
return 0;
}
输出:
sizeof *s: 16
offsetof(struct s, array): 12
0
最佳答案
简短答案
答案适用于
C99 (TC3)
和C11 (TC1)
。长答案
FAM最初是在C99(TC0)(1999年12月)中引入的,其原始规范要求FAM的偏移量在结构的末尾。原始规范定义明确,因此不会导致不确定的行为,也不会成为严格遵循标准的问题。
C99 (TC0) §6.7.2.1 p16
(1999年12月)问题在于常见的C99实现(例如GCC)未遵循标准要求,并允许FAM覆盖任何尾随填充字节。他们的方法被认为是更有效的,并且由于他们遵循该标准的要求(会导致向后兼容的破坏),委员会选择更改该规范,并且从C99 TC2(2004年11月)开始不再需要该标准。 FAM在结构末尾的偏移量。
C99 (TC2) §6.7.2.1 p16
(2004年11月)新规范删除了要求FAM偏移在结构末尾的声明,并且引入了非常不幸的结果,因为该标准赋予实现自由,不保留结构或结构中任何填充字节的值。工会处于一致状态。进一步来说:
C99 (TC3) §6.2.6.1 p6
这意味着,如果我们的FAM元素中的任何一个与(或覆盖)任何尾随填充字节相对应,则在存储到该结构的成员时,它们(可能)采用未指定的值。我们甚至不必考虑这是否适用于FAM本身存储的值,即使严格解释为仅适用于FAM以外的成员,也足以造成损害。
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
int main(void) {
struct s {
size_t len;
char pad;
int array[];
};
struct s *s = malloc(sizeof *s + sizeof *s->array);
if (sizeof *s > offsetof(struct s, array)) {
s->array[0] = 123;
s->len = 1; /* any padding bytes take unspecified values */
printf("%d\n", s->array[0]); /* indeterminate value */
}
free(s);
return 0;
}
一旦我们存储到该结构的成员,填充字节就占据未指定的字节,因此对与任何尾随填充字节相对应的FAM元素的值所做的任何假设现在都是错误的。这意味着任何假设都会导致我们无法严格遵守。
未定义的行为
尽管填充字节的值是“未指定的值”,但是对于受它们影响的类型不能说相同,因为基于未指定值的对象表示可以生成陷阱表示。因此,描述这两种可能性的唯一标准术语是“不确定值”。如果FAM的类型恰好具有陷阱表示,则访问它不仅是未指定值的问题,而且是未定义行为的问题。
但是,等等,还有更多。如果我们同意描述该值的唯一标准术语是“不确定值”,那么即使FAM的类型碰巧没有陷阱表示,由于对C的正式解释,我们也达到了不确定的行为。标准委员会认为,将不确定的值传递给标准库函数是未定义的行为。