在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
零元素数据数组的需求和目的是什么?
在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
零元素数据数组的需求和目的是什么?
Answers:
这是一种具有可变大小的数据的方法,而不必调用两次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 */
请注意,您没有提及该data
字段的任何大小。还请注意,此特殊变量只能出现在结构的末尾。
在C99中,此问题在6.7.2.1.16(强调我的)中进行了解释:
作为一种特殊情况,具有多个命名成员的结构的最后一个元素可能具有不完整的数组类型。这称为灵活数组成员。在大多数情况下,柔性数组成员将被忽略。特别地,该结构的尺寸就好像省略了柔性阵列构件,除了其可能具有比省略所暗示的更多的尾随填充。但是,当一个。(或->)运算符具有一个左操作数,该操作数是具有灵活数组成员的结构(指向该结构的指针),并具有该成员的右操作数名称,其行为就像该成员被最长的数组(具有相同的元素类型)所替换)不会使结构大于被访问的对象;即使该阵列的偏移量与替换阵列的偏移量不同,其偏移量也应保持为柔性阵列成员的偏移量。如果此数组没有元素,
换句话说,如果您有:
struct something
{
/* other variables */
char data[];
}
struct something *var = malloc(sizeof(*var) + extra);
您可以使用var->data
中的索引进行访问[0, extra)
。请注意,sizeof(struct something)
这只会考虑其他变量data
的大小,即大小为0。
还可能需要注意的是,该标准实际上是如何举例说明malloc
这种构造的(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的偏移量可能不同)。
[0, extra)
啊
也称为struct hack。
所以下一次,我会说:
struct bts_action *bts = malloc(sizeof(struct bts_action) + sizeof(char)*100);
相当于说:
struct bts_action{
u16 type;
u16 size;
u8 data[100];
};
而且我可以创建任意数量的这种struct对象。
这个想法是在结构的末尾允许一个可变大小的数组。大概bts_action
是一些具有固定大小的标头(type
和size
字段)和可变大小的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
malloc
使您可以多次重复自己,如果action
更改类型,则必须多次修复。您自己比较以下两个,就会知道:struct some_thing *variable = (struct some_thing *)malloc(10 * sizeof(struct some_thing));
与struct some_thing *variable = malloc(10 * sizeof(*variable));
第二个相比,第二个更短,更清洁并且很容易更改。
该代码不是有效的C(请参阅)。出于显而易见的原因,Linux内核丝毫不关心可移植性,因此它使用了大量非标准代码。
他们正在做的是一个数组大小为0的GCC非标准扩展。一个符合标准的程序就已经编写了u8 data[];
,这意味着完全相同的事情。Linux内核的作者显然喜欢使事情变得不必要的复杂和非标准,如果可以这样做的话。
在较早的C标准中,以空数组结尾的结构称为“结构hack”。其他人已经在其他答案中解释了其目的。在C90标准中,结构hack是未定义的行为,并且可能导致崩溃,主要是因为C编译器可以在结构的末尾随意添加任意数量的填充字节。这样的填充字节可能与您试图在结构末尾“入侵”的数据相冲突。
GCC早期进行了非标准的扩展,以将其从未定义的行为更改为定义良好的行为。然后,C99标准适应了这一概念,因此任何现代C程序都可以毫无风险地使用此功能。在C99 / C11中称为柔性阵列成员。
零长度数组的另一种用法是用作结构内部的命名标签,以帮助进行编译时结构偏移量检查。
假设您有一些较大的结构定义(跨越多条缓存行),要确保它们在与边界交叉的开头和中间与缓存行边界对齐。
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);
不用这样,您可以在结构中声明一个零长度的数组,该数组用作具有一致名称但不占用任何空间的命名标签。
#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);