数组结构体末尾需要空大括号“{}”的原因是什么?

61

我在 Linux 内核中编写了一些 代码,具体位置可以参考此处

static struct ctl_table ip_ct_sysctl_table[] = {
    {
        .procname   = "ip_conntrack_max",
        .maxlen     = sizeof(int),
        .mode       = 0644,
        .proc_handler   = proc_dointvec,
    },
    // ...
    {
        .procname   = "ip_conntrack_log_invalid",
        .maxlen     = sizeof(unsigned int),
        .mode       = 0644,
        .proc_handler   = proc_dointvec_minmax,
        .extra1     = &log_invalid_proto_min,
        .extra2     = &log_invalid_proto_max,
    },
    { }
};

这里的结构体数组以 { } 结尾,它的目的是什么?
顺便提一下,这段代码上面还有另一个 结构体数组,但末尾没有空括号。

在什么情况下应该在结构体数组末尾使用空括号?


1
哦,如果添加一个类似于 0 信号表示数组的结尾,那会怎么样呢?只是猜测。 - Eraklon
4
这是一些非标准的GCC扩展。因此,它很可能没有或很少有文档......我刚刚读了所有文档,但找不到关于空结构初始化列表的任何内容。然而,它可以编译,除非你使用“-pedantic”强制启用严格的ISO标准。 这是一种非标准的GCC扩展功能,因此可能缺乏相关文档说明。尽管我已阅读所有文档,但并未找到关于空结构体初始化列表的内容。但是,除非您使用“-pedantic”强制启用ISO标准模式,否则代码将编译通过。 - Lundin
9
无论如何,它是一个“哨兵”值,即将数组结尾标记为零/空的项。 - Lundin
哨兵在CPython扩展模块中也很常见。 - MaxPowers
4个回答

39

这个特定的更改是由Eric W. Biederman在sysctl net: Remove unused binary sysctl code提交中的一部分,将ip_ct_sysctl_table数组的最后一个元素的初始化从{0}更改为{}(并对许多其他数组初始化进行了类似的更改)。

{0}模式似乎已经存在很长时间了,而且在Linux源代码中,{0}{}作为数组的终止元素初始化通常明确地被称为“终止条目”,因此它很可能是一个存在的模式,允许在不知道它们的长度的情况下使用这些数组,在遇到零初始化的终止条目时终止使用。例如,在sound/aoa/fabrics/snd-aoa-fabric-layout.c中的类似数组中,零初始化的意图甚至在注释中明确提到,例如:

static struct codec_connection toonie_connections[] = {
  {
      .connected = CC_SPEAKERS | CC_HEADPHONE,
      .codec_bit = 0,
  },
  {} /* terminate array by .connected == 0 */
};

11
他们放弃标准C而选择GCC扩展的理由很有趣,值得探究。该扩展在功能方面与标准C完全相同,但它会阻止代码在标准C编译器上进行编译。据说这两者是100%等价的,因为gcc似乎没有记录这个特性... 这不是零长度数组,而是一个空初始化列表。 - Lundin
@Lundin 另一方面,我不明白为什么C标准委员会不将{}合法化为成员初始化为0。{0}的缺点是不够通用,因为它要求第一个成员不是聚合体。 - jamesdlin
1
@Lundin:然而,cppreference页面与ISO/IEC 9899:2011的措辞存在冲突,后者允许这样做(§6.7.9(21))。没有初始化器无疑比聚合体的成员“少”。因此,这不是奇怪的编译器扩展,而是合法的C语言。 - Damon
2
@Damon 这不是有效的C语言,这是众所周知的...请使用gcc -pedantic-errors进行编译。要了解原因,您需要阅读初始化程序列表的实际语法,即6.7.9顶部。必须至少有一个初始化程序。在此处解释:https://dev59.com/EmMm5IYBdhLWcg3wdu5V。具体来说,`{initializer-list}` 然后是初始化程序列表:designation(可选)initializerinitializer-list, designation(可选)initializer - Lundin
2
@Lundin 在这个特定的情况下,我不知道。但是gcc扩展在Linux内核中被广泛使用。 - bobsburner
显示剩余8条评论

21

您可能熟悉零结尾字符串。 ctl_table ip_ct_sysctl_table[]是一个零结尾的数组,即最后一个数组条目具有全零成员。


1
当遍历数组时,你会知道已经到达末尾的条件是procname为空或者maxlen为零。 - Paul Ogilvie
1
@PaulOgilvie:嗯,这个例子是不完整的。如果procname是一个char[100],那么它就是"",而不是null。但除此之外没错。 - MSalters

13
需要在结构体数组末尾添加空括号 '{ }' 吗?
明确一点: 在C语言中,"在结构体数组末尾添加空括号 '{ }'" 并不是必需的。
何时应该在结构体数组末尾使用空括号?
当代码需要 哨兵值 时。
有时程序需要一个全为零的最终数组元素-用于检测结束。这种需求来自于应用程序对数组 ctl_table ip_ct_sysctl_table[] 的使用,而不是来自于C语言本身的需求。

9

这是一个数组末尾初始化为零的元素,旨在通过增加一位来增加数组的元素数量。

考虑下面这个小例子:

#include <stdio.h>

struct Test
{
  int x;
  int y;
} arr[] =
{
    {1,2},
    {3,4},
//  {}
};

int main(void) {
    printf("%zu\n", sizeof(arr) / sizeof(arr[0]));
    return 0;
}
arr 数组的大小会因为在数组初始值列表结尾取消注释 {} 而改变。

输出:

使用 // {}(数组有2个元素)

2

使用 {}(数组有3个元素)

3

进一步解释:

ip_ct_sysctl_table 数组只在一个地方使用,即此处:

in->ctl_table = kmemdup(ip_ct_sysctl_table,
                sizeof(ip_ct_sysctl_table),
                GFP_KERNEL);

额外的{}增加了总大小ip_ct_sysctl_table


1
这不是“为了增加数组元素的数量”,而是为了标志数组的结束。 - Paul Ogilvie
6
不,这个想法是迄今为止没有人能够完全解释它的确切含义。最接近确定性的陈述就是{}是一个初始化器。但是为什么会这样还不清楚。因此,目前来看,使用"可能"这个词可能是个好主意。 :) - ryyker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接