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

Jee*_*tel 117 c structure

在Linux内核代码中,我发现了以下无法理解的内容.

 struct bts_action {
         u16 type;
         u16 size;
         u8 data[0];
 } __attribute__ ((packed));
Run Code Online (Sandbox Code Playgroud)

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

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

Sha*_*baz 133

这是一种可变数据大小的方法,无需调用malloc(kmalloc在本例中)两次.你会像这样使用它:

struct bts_action *var = kmalloc(sizeof(*var) + extra, GFP_KERNEL);
Run Code Online (Sandbox Code Playgroud)

这曾经不是标准的,被认为是黑客(如Aniket所说),但它在C99中标准化.它现在的标准格式是:

struct bts_action {
     u16 type;
     u16 size;
     u8 data[];
} __attribute__ ((packed)); /* Note: the __attribute__ is irrelevant here */
Run Code Online (Sandbox Code Playgroud)

请注意,您没有提到该data字段的任何大小.另请注意,此特殊变量只能位于结构的末尾.


在C99中,这个问题在6.7.2.1.16中解释(强调我的):

作为一种特殊情况,具有多个命名成员的结构的最后一个元素可能具有不完整的数组类型; 这被称为灵活的阵列成员.在大多数情况下,将忽略灵活数组成员.特别地,结构的尺寸好像省略了柔性阵列构件,除了它可以具有比省略意味着更多的拖尾填充.但是,当一个.(或 - >)运算符有一个左操作数,它是一个带有灵活数组成员的结构(一个指针),右操作数命名该成员,它的行为就好像该成员被最长的数组替换(具有相同的元素类型) )不会使结构大于被访问的对象; 数组的偏移量应保持为灵活数组成员的偏移量,即使这与替换数组的偏移量不同.如果这个数组就没有的元素,它的行为就好像它有一个元素,但如果任何试图访问该元素或产生一个指向一个过去,它的行为是不确定的.

或者换句话说,如果你有:

struct something
{
    /* other variables */
    char data[];
}

struct something *var = malloc(sizeof(*var) + extra);
Run Code Online (Sandbox Code Playgroud)

您可以使用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]));
Run Code Online (Sandbox Code Playgroud)

该标准在同一地点的另一个有趣的注释是(强调我的):

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

struct { int n; double d[m]; } *p;
Run Code Online (Sandbox Code Playgroud)

(在某些情况下,这种等价性被破坏;特别是,成员d的偏移量可能不同).

  • @JL2210,https://en.wikipedia.org/wiki/Interval_(mathematics)#Terminology (2认同)

Ani*_*nge 36

实际上,对于GCC(C90)来说,这实际上是一个黑客攻击.

它也被称为结构黑客.

所以下一次,我会说:

struct bts_action *bts = malloc(sizeof(struct bts_action) + sizeof(char)*100);
Run Code Online (Sandbox Code Playgroud)

这相当于说:

struct bts_action{
    u16 type;
    u16 size;
    u8 data[100];
};
Run Code Online (Sandbox Code Playgroud)

我可以创建任意数量的此类结构对象.


she*_*heu 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);
Run Code Online (Sandbox Code Playgroud)

另见:http://c2.com/cgi/wiki ?StructHack

  • @Aniket:我不确定从哪里来的*那个想法. (2认同)
  • @sheu,它来自这样一个事实,即你的写作'malloc`的风格让你多次重复自己,如果`action`的类型改变了,你必须多次修复它.比较以下两个你自己,你会知道:`struct some_thing*variable =(struct some_thing*)malloc(10*sizeof(struct some_thing));`vs`struct some_thing*variable = malloc(10*sizeof(*variable) ));`第二个更短,更清洁,更容易改变. (2认同)

Lun*_*din 5

代码无效C(见此).出于显而易见的原因,Linux内核没有丝毫关注可移植性,因此它使用了大量非标准代码.

他们正在做的是一个数组大小为0的GCC非标准扩展.一个符合标准的程序就已经编写了u8 data[];,它意味着完全相同的东西.Linux内核的作者显然喜欢让事情变得不必要地复杂和非标准,如果这样做的选择显露出来的话.

在较旧的C标准中,以空数组结束结构被称为"结构黑客".其他人已经在其他答案中解释了其目的.在C90标准中,struct hack是未定义的行为并且可能导致崩溃,主要是因为C编译器可以在结构的末尾自由添加任意数量的填充字节.这样的填充字节可能会与您尝试在结构末端"入侵"的数据发生冲突.

GCC早期做了一个非标准的扩展,将其从未定义的行为转变为定义明确的行为.然后,C99标准采用了这一概念,因此任何现代C程序都可以无风险地使用此功能.它被称为C99/C11中的柔性阵列构件.

  • Linux和gcc确实是不可分割的.Linux内核也很难理解(主要是因为OS无论如何都很复杂).我的观点是,说"Linux内核的作者显然喜欢让事情变得不必要地复杂和非标准,如果这样做的选择能够显示出来"由于第三方错误的编码实践而不好. (4认同)
  • 我怀疑"Linux内核不关心可移植性".也许你的意思是对其他编译器的可移植性?确实,它与gcc的功能完全交织在一起. (3认同)
  • 尽管如此,我认为这段特殊的代码不是主流代码,可能因为其作者没有太多关注它而被遗漏.许可证说它关于一些德州仪器驱动程序,因此内核的核心程序员不太关注它.我很确定内核开发人员会根据新标准或新的优化不断更新旧代码.它太大了,无法确保一切都更新! (3认同)
  • @Shahbaz至于任何标记为德州仪器(TI)的案例,TI本身都是因为他们在各种TI芯片的应用笔记中产生了有史以来最无用,最糟糕,最天真的C代码而臭名昭着.如果代码来自TI,则所有关于解释对其有用的内容的赌注都将被取消. (3认同)
  • @Shahbaz 对于“明显”部分,我的意思是可移植到其他操作系统,这自然没有任何意义。但是他们似乎也不关心其他编译器的可移植性,他们使用了如此多的 GCC 扩展,以至于 Linux 不可能被移植到另一个编译器。 (2认同)