什么是零元素阵列的需要?

时间:2013-02-01 09:42:14

标签: c structure

在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

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

5 个答案:

答案 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)

实际上这对于GCCC90)来说实际上是黑客攻击。

它也被称为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是一些具有固定大小标头(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

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