如何初始化具有可变数组成员的结构体

36

我有以下结构

typedef struct _person {
    int age;
    char sex;
    char name[];
}person;

我在互联网上进行了一些基本搜索(但没有成功),关于如何创建一个含有可变数组成员的结构体实例并初始化它,而不使用malloc()

例如:对于普通的结构体,像:

struct a {
    int age; 
    int sex;
};

我们可以创建一个struct a的实例,并像这样进行初始化:

struct a p1 = {10, 'm'};

对于包含灵活数组的结构体(如上面提到的_person),我们如何像普通 structures 一样创建实例和初始化呢?

这是否可能?如果是,我们如何在初始化期间传递数组大小以及要初始化的实际值?

还是说,根据 C99 规范中所述的 - 6.7.2.1 结构体和联合类型说明符 - 第17点,唯一创建具有灵活数组的结构体的方法是使用 malloc() ?!


1
你不能这样做,结构体必须在编译时确定大小。 - Anycorn
8
结构体中含有柔性数组成员的确会在编译期确定大小。 - CB Bailey
4
GCC提供了一个扩展,可以让你像这样做:struct { size_t len; int data[]; } x = { 4, { 1, 2, 3, 4 } };,它可以工作,但不是可移植的。你可以研究一下你平台上的alloca版本,可能会有更加可移植的解决方案,但你需要确保它们都以相同方式运作并具有相同的实现特性。 - Chris Lutz
@Charles 不是指海报的意思 - 必须有一些类似于 malloc 或等效的东西。 - Anycorn
2
这篇关于编程的文章介绍了GCC编译器中的Zero-Length选项。该选项似乎默认开启(假设您没有使用“-ansi”或任何禁用它的选项)。 - Chris Lutz
显示剩余2条评论
3个回答

15

不,可变长数组必须手动分配内存。但是您可以使用calloc来初始化可变部分,并使用复合字面量来初始化固定部分。我会把它封装在一个分配inline函数中,像这样:

typedef struct person {
  unsigned age;
  char sex;
  size_t size;
  char name[];
} person;

inline
person* alloc_person(int a, char s, size_t n) {
  person * ret = calloc(sizeof(person) + n, 1);
  if (ret) memcpy(ret,
                  &(person const){ .age = a, .sex = s, .size = n},
                  sizeof(person));
  return ret;
}

注意,这使得检查内存分配是否成功的任务留给了调用者。

如果您不需要像我在这里包含的size字段,甚至可以使用宏。只是在执行memcpy之前无法检查calloc的返回值。到目前为止,我编程的所有系统都会相对顺利地中止。一般而言,我认为检查malloc的返回值并不重要,但在这个问题上意见大相径庭。

也许(在这种特殊情况下)这可以为优化器提供更多将代码集成到周围环境中的机会:

#define ALLOC_PERSON(A,  S,  N)                                 \
((person*)memcpy(calloc(sizeof(person) + (N), 1),               \
                 &(person const){ .age = (A), .sex = (S) },     \
                 sizeof(person)))

编辑:AS是编译时常量时,这种方法可能比函数更好。在这种情况下,由于复合字面量被const修饰,它可以被静态地分配,并且其初始化可以在编译时完成。此外,如果代码中出现多个具有相同值的分配,则编译器将允许仅实现该复合字面量的单个副本。


3
直接复制calloc()的结果是危险的;如果分配失败,你会得到核心转储(或其他未定义的行为,这不太可能是你想要的结果)。 - Jonathan Leffler
@JonathanLeffler,好的,我会进行修改。我会将其整合到函数中。至于宏,我只需要提到我关于检查malloc返回值的通用说明即可。 - Jens Gustedt
你的内联函数解决方案很棒。在我看来,宏没有任何优势。它难以阅读,不检查calloc返回值,并且性能也不会更好。宏通常不比内联函数表现更好(有时甚至更差——考虑将strlen()传递给宏,这会导致其被计算两次)。 - ugoren
@ugoren,带有 const 限定符的复合字面量在优化方面非常特殊。因此可以进行更多的优化,请查看我的编辑内容。可以尝试通过编写一个类似于 calloc_and_copy_or_fail 的函数来结合这两种方法。 - Jens Gustedt
1
不错。顺便说一句,比起 person *ret = calloc(sizeof(person) + n, 1);,更安全的习惯是写成 person *ret = calloc(sizeof(*ret) + (sizeof(ret->name[0]) * n), 1); - Todd Lehman
显示剩余2条评论

7

有一些技巧可以使用。这取决于您的特定应用程序。

如果您想初始化单个变量,可以定义正确大小的结构:

   struct  {
        int age;
        char sex;
        char name[sizeof("THE_NAME")];
    } your_variable = { 55, 'M', "THE_NAME" };

问题在于你必须使用指针转换将变量解释为"person"(例如"*(person *)(&your_variable)")。但是你可以使用一个包含union来避免这个问题:
union {
 struct { ..., char name[sizeof("THE_NAME")]; } x;
 person p;
} your_var = { 55, 'M', "THE_NAME" };

因此,your_var.p 是类型为“person”的。您也可以使用宏来定义您的初始值设定项,这样您只需编写一次该字符串:

#define INIVAR(x_, age_, sex_ ,s_) \
   union {\
     struct { ..., char name[sizeof(s_)]; } x;\
     person p;\
    } x_ = { (age_), (sex_), (s_) }

INIVAR(your_var, 55, 'M', "THE NAME");

另一个问题是这个技巧不适用于创建"person"数组。数组的问题在于所有元素必须具有相同的大小。在这种情况下,使用const char *而不是char[]会更安全。或者使用动态分配 ;)


6
一种带有灵活数组成员的结构类型可以被视为省略了灵活数组成员,因此您可以像这样初始化结构体。
person p = { 10, 'x' };

然而,弹性数组中没有成员被分配,任何尝试访问弹性数组的成员或形成指向其末尾之外的成员的指针都是无效的。创建具有实际上在该数组中具有元素的弹性数组成员结构的实例的唯一方法是为其动态分配内存,例如使用 malloc


4
如果你感兴趣的话,有一个GCC扩展可以让你使用相同的语法来指定灵活数组成员。 - Chris Lutz

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