在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
零元素数据数组的需求和目的是什么?
答案 0 :(得分:131)
这是一种拥有可变数据大小的方法,无需在此情况下调用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的偏移量可能不相同。)
答案 1 :(得分:36)
它也被称为struct hack。
所以下一次,我会说:
struct bts_action *bts = malloc(sizeof(struct bts_action) + sizeof(char)*100);
这相当于说:
struct bts_action{
u16 type;
u16 size;
u8 data[100];
};
我可以创建任意数量的此类结构对象。
答案 2 :(得分:7)
我们的想法是在结构的末尾允许一个可变大小的数组。据推测,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);
答案 3 :(得分:5)
代码无效C(see this)。出于显而易见的原因,Linux内核没有丝毫关注可移植性,所以它使用了大量非标准代码。
他们正在做的是数组大小为0的GCC非标准扩展。符合标准的程序将编写u8 data[];
,这意味着完全相同的事情。 Linux内核的作者显然喜欢让事情变得不必要地复杂和非标准,如果这样做的选择就会显现出来。
在较旧的C标准中,以空数组结束结构称为“结构黑客”。其他人已经在其他答案中解释了其目的。在C90标准中,struct hack是未定义的行为并且可能导致崩溃,主要是因为C编译器可以在结构的末尾自由添加任意数量的填充字节。这样的填充字节可能会与您尝试在结构末端“入侵”的数据发生冲突。
早期的GCC做了一个非标准的扩展,将其从未定义的行为改为明确定义的行为。然后,C99标准采用了这一概念,因此任何现代C程序都可以无风险地使用此功能。它在C99 / C11中称为灵活数组成员。答案 4 :(得分:1)
另一个不常见的零长度数组用法是在结构中获取命名标签。
假设您有一些大型结构定义(跨越多个高速缓存行),您希望确保它们在跨越边界的开始和中间都与高速缓存行边界对齐。
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);