C 什么';需要零元素数组吗?
在Linux内核代码中,我发现了以下我无法理解的东西C 什么';需要零元素数组吗?,c,structure,flexible-array-member,C,Structure,Flexible Array Member,在Linux内核代码中,我发现了以下我无法理解的东西 struct bts_action { u16 type; u16 size; u8 data[0]; } __attribute__ ((packed)); 代码如下: 零元素数据数组的需求和用途是什么?这实际上是一个黑客行为,实际上是针对()的 它也叫a 所以下一次,我会说: struct bts_action *bts = malloc(sizeof(struct bts_ac
struct bts_action {
u16 type;
u16 size;
u8 data[0];
} __attribute__ ((packed));
代码如下:
零元素数据数组的需求和用途是什么?这实际上是一个黑客行为,实际上是针对()的 它也叫a 所以下一次,我会说:
struct bts_action *bts = malloc(sizeof(struct bts_action) + sizeof(char)*100);
这相当于说:
struct bts_action{
u16 type;
u16 size;
u8 data[100];
};
我可以创建任意数量的这样的结构对象。思想是在结构的末尾允许一个大小可变的数组。大概,
bts_action
是一个具有固定大小报头(类型
和大小
字段)和可变大小数据
成员的数据包。通过将其声明为0长度数组,它可以像任何其他数组一样进行索引。然后分配一个bts\u动作
struct,比如1024字节数据
大小,如下所示:
size_t size = 1024;
struct bts_action* action = (struct bts_action*)malloc(sizeof(struct bts_action) + size);
另请参见:这是一种数据大小可变的方法,无需调用
malloc
(kmalloc
)两次。您可以这样使用它:
struct bts_action *var = kmalloc(sizeof(*var) + extra, GFP_KERNEL);
这曾经不是标准的,被认为是一种黑客行为(正如Aniket所说),但它在C99中被标准化了。现在的标准格式是:
struct bts_action {
u16 type;
u16 size;
u8 data[];
} __attribute__ ((packed)); /* Note: the __attribute__ is irrelevant here */
请注意,数据
字段没有提到任何大小。还要注意,这个特殊变量只能出现在结构的末尾
在C99中,这一问题在6.7.2.1.16(重点)中解释: 作为特例,具有多个命名成员的结构的最后一个元素可以 具有不完整的数组类型这称为灵活数组成员。在大多数情况下, 将忽略灵活数组成员。特别是,结构的大小就像 省略了flexible数组成员,只是它的尾部填充可能比 这一遗漏意味着。然而,当一个。(或->)运算符的左操作数为 (指向)具有灵活数组成员和正确操作数名称的结构 成员,其行为就好像该成员被替换为最长的数组(具有相同的 元素类型),不会使结构大于正在访问的对象;这个 阵列的偏移量应保持柔性阵列构件的偏移量,即使这会有所不同 从替换阵列的。如果这个数组没有元素,它的行为就像 它只有一个元素,但如果试图访问该元素,则行为未定义 元素或来生成一个指针,该指针超过它一个 或者换句话说,如果您有:
struct something
{
/* other variables */
char data[];
}
struct something *var = malloc(sizeof(*var) + extra);
您可以使用[0,额外)
中的索引访问var->data
。请注意,sizeof(struct something)
只会给出其他变量的大小,即给data
一个0的大小
还值得注意的是,本标准实际给出了
malloc
ing此类构造的示例(6.7.2.1.17):
标准在同一位置的另一个有趣的注释是(我的重点):
假设对malloc的调用成功,在大多数情况下,p指向的对象的行为就好像p被声明为:
struct { int n; double d[m]; } *p;
(在某些情况下,这种等效性被破坏;特别是,成员d的偏移量可能不同)
代码不是有效的C()。Linux内核显然不关心可移植性,因此它使用了大量非标准代码
他们正在做的是一个数组大小为0的GCC非标准扩展。一个符合标准的程序将编写u8 data[];
,这意味着同样的事情。Linux内核的作者显然喜欢让事情变得不必要的复杂和非标准,如果这样做的选项暴露出来的话
在旧的C标准中,以空数组结束结构被称为“结构破解”。其他人已经在其他答案中解释了它的用途。C90标准中的结构破解是未定义的行为,可能会导致崩溃,主要是因为C编译器可以在结构的末尾添加任意数量的填充字节。此类填充字节可能会与您试图在结构的末尾“破解”的数据发生冲突
GCC早期做了一个非标准扩展,将其从未定义行为更改为定义良好的行为。C99标准随后采用了此概念,因此任何现代C程序都可以使用此功能而不存在风险。它在C99/C11中被称为灵活数组成员。零长度数组的另一种用法是作为ASIS结构内部的命名标签t编译时结构偏移量检查
假设您有一些大的结构定义(跨越多个高速缓存行),以确保它们在起点和中间都对齐到高速缓存行边界。
struct example_large_s
{
u32 first; // align to CL
u32 data;
....
u64 *second; // align to second CL after the first one
....
};
在代码中,您可以使用GCC扩展声明它们,如:
__attribute__((aligned(CACHE_LINE_BYTES)))
但您仍然希望确保在运行时强制执行此操作
ASSERT (offsetof (example_large_s, first) == 0);
ASSERT (offsetof (example_large_s, second) == CACHE_LINE_BYTES);
这适用于单个结构,但很难涵盖多个结构,每个结构都有不同的成员名称要对齐。您很可能会得到如下代码,其中必须找到每个结构的第一个成员的名称:
assert (offsetof (one_struct, <name_of_first_member>) == 0);
assert (offsetof (one_struct, <name_of_second_member>) == CACHE_LINE_BYTES);
assert (offsetof (another_struct, <name_of_first_member>) == 0);
assert (offsetof (another_struct, <name_of_second_member>) == CACHE_LINE_BYTES);
那么运行时断言代码将更易于维护:
assert (offsetof (one_struct, cacheline0) == 0);
assert (offsetof (one_struct, cacheline1) == CACHE_LINE_BYTES);
assert (offsetof (another_struct, cacheline0) == 0);
assert (offsetof (another_struct, cacheline1) == CACHE_LINE_BYTES);
我不确定是否应该有一个或标记…@hippietrail,因为当有人问这个结构是什么时,他们通常不知道它被称为“flexible array member”。如果他们这样做了,他们可以很容易找到答案。因为他们没有,所以他们不能给问题贴上这样的标签。这就是为什么我们没有这样的标签。投票重新打开。我同意这不是重复,因为其他帖子都没有提到非标准“结构黑客”的组合长度为零,定义良好的C99具有灵活的数组成员。我还认为,C编程社区了解任何obsc都是有益的
#define CACHE_LINE_ALIGN_MARK(mark) u8 mark[0] __attribute__((aligned(CACHE_LINE_BYTES)))
struct example_large_s
{
CACHE_LINE_ALIGN_MARK (cacheline0);
u32 first; // align to CL
u32 data;
....
CACHE_LINE_ALIGN_MARK (cacheline1);
u64 *second; // align to second CL after the first one
....
};
assert (offsetof (one_struct, cacheline0) == 0);
assert (offsetof (one_struct, cacheline1) == CACHE_LINE_BYTES);
assert (offsetof (another_struct, cacheline0) == 0);
assert (offsetof (another_struct, cacheline1) == CACHE_LINE_BYTES);