有零元素的数组需要什么?

在Linux内核代码中,我发现了以下我无法理解的东西。

struct bts_action { u16 type; u16 size; u8 data[0]; } __attribute__ ((packed)); 

代码在这里: http : //lxr.free-electrons.com/source/include/linux/ti_wilink_st.h

什么是零元素的数据数组的需求和目的?

这是一种可以resize的数据的方法,而不必在这种情况下调用mallockmalloc )两次。 你会这样使用它:

 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 */ 

请注意,您没有提及data字段的大小。 还要注意,这个特殊的variables只能出现在结构的末尾。


在C99中,这个问题在6.7.2.1.16(我的重点)中有解释:

作为特殊情况,具有多个名称成员的结构的最后一个元素可能具有不完整的数组types; 这被称为一个灵活的数组成员 。 在大多数情况下,灵活的数组成员被忽略。 特别是,结构的大小就好像是柔性arrays成员被省略,除了它可能具有比省略暗示的更多的尾部填充。 但是,当一个。 (或 – >)运算符的左操作数是(指向)一个具有灵活数组成员的结构,右操作数指定该成员,它的行为就好像该成员被replace为最长的数组(具有相同的元素types)不会使结构大于被访问的对象; 数组的偏移量应保持为可变数组成员的偏移量,即使这与replace数组的偏移量不同。 如果这个数组没有元素,就像它有一个元素一样,但是行为是不确定的,如果试图访问那个元素或者生成一个指向它的指针。

换句话说,如果你有:

 struct something { /* other variables */ char data[]; } struct something *var = malloc(sizeof(*var) + extra); 

你可以使用[0, extra)索引访问var->data 。 请注意, sizeof(struct something)只会给出其他variables的大小,即data大小为0。


注意这个标准实际上给出了如何构造这样一个构造(6.7.2.1.17)的例子也许是有趣的:

 struct s { int n; double d[]; }; int m = /* some value */; struct s *p = malloc(sizeof (struct s) + sizeof (double [m])); 

标准在同一地点的另一个有趣的笔记是(强调我的):

假设对malloc的调用成功了,p指向的对象在大多数情况下的行为就像p被声明为:

 struct { int n; double d[m]; } *p; 

(在某些情况下,这种等价性被打破了,尤其是成员d的偏移量可能不一样 )。

事实上,这对于GCC ( C90 )来说实际上是一个破解。

这也被称为结构黑客 。

所以下一次,我会说:

 struct bts_action *bts = malloc(sizeof(struct bts_action) + sizeof(char)*100); 

这将相当于说:

 struct bts_action{ u16 type; u16 size; u8 data[100]; }; 

我可以创build任何数量的这样的结构对象。

这个想法是允许在结构的末尾有一个可变大小的数组。 据推测, bts_action是一些固定大小的数据包( typesize字段)和可变大小的data成员。 通过声明它为0长度的数组,它可以像任何其他数组一样进行索引。 然后你会分配一个bts_action结构,比如1024字节的data大小,就像这样:

 size_t size = 1024; struct bts_action* action = (struct bts_action*)malloc(sizeof(struct bts_action) + size); 

另请参阅: http : //c2.com/cgi/wiki?StructHack

代码是无效的C( 见这个 )。 Linux内核由于显而易见的原因,并没有丝毫关心可移植性,所以它使用了大量的非标准代码。

他们正在做的是一个GCC非标准扩展,数组大小为0.一个符合标准的程序将会写入u8 data[]; 这将意味着同样的事情。 Linux内核的作者显然喜欢把事情变得不必要的复杂和不规范,如果有这样一个选项的话。

在较老的C标准中,用一个空数组结尾结构被称为“struct hack”。 其他人已经在其他答案中解释了它的目的。 C90标准中的struct hack是未定义的行为,可能会导致崩溃,主要是因为C编译器可以在结构的末尾添加任意数量的填充字节。 这样的填充字节可能与您试图在结构的末尾“破解”的数据相冲突。

GCC早期做了一个非标准的扩展,将其从未定义的行为改变为明确定义的行为。 然后C99标准适应了这个概念,任何现代C程序都可以使用这个特性而没有风险。 它在C99 / C11中被称为灵活的数组成员

另一个不常见的使用零长度数组的方法是在结构体中获得一个命名标签。

假设你有一些大的结构定义(跨越多个caching行),你要确保它们alignment,以caching跨越边界的开始和中间的行边界。

 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); 

这可以用于单个结构,但是很难涵盖许多结构,每个结构都有不同的成员名称。 你很可能会得到像下面的代码,你必须find每个结构的第一个成员的名字:

 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); 

您可以在结构中声明一个长度为零的数组,作为具有一致名称的命名标签,但不占用任何空间。

 #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);