结构体中使用指针和零元素数组的区别

4

这两种实现有何不同:

struct queue {
    int a;
    int b;
    q_info *array;
};

并且
struct queue {
    int a;
    int b;
    q_info array[0];
};

4
你想对 q_info array[0]; 做什么聪明的事情? - Stephane Rolland
3
它们的不同之处在于 T array[0] 是未定义行为。 - user529758
4
值得注意的是,[0] 是非标准用法(尽管被 gcc 和 msvc 支持)。据我记得,C99 允许使用 some_type some_array[]; 来达到同样的目的。在此之前,通常会使用 [1],稍后再通过 sizeof 进行操作。 - keltar
@keltar 的确,6.7.5.2:1 在“Constraints”(约束)部分中,因此需要符合标准的编译器发出诊断,但之后发生的行为是未定义的(顺便说一下,GCC并不发出诊断)。 - Pascal Cuoq
@keltar 至少 [0] 明确表明期望发生某些魔法。标准兼容的声明 [1],其中预期使用可变数组成员,通常在结构体的后续使用中会出现未定义的行为:http://blog.frama-c.com/index.php?post/2013/07/31/From-Pascal-strings-to-Python-tuples - Pascal Cuoq
显示剩余12条评论
5个回答

11

第二个struct没有使用零元素数组——这是在C99之前制作灵活数组成员的技巧。区别在于,在第一个片段中,您需要两个malloc——一个为struct,另一个为array;而在第二个片段中,您可以在单个malloc中完成两者:

size_t num_entries = 100;
struct queue *myQueue = malloc(sizeof(struct queue)+sizeof(q_info)*num_entries);

代替

size_t num_entries = 100;
struct queue *myQueue = malloc(sizeof(struct queue));
myQueue->array = malloc(sizeof(q_info)*num_entries);

这样可以减少dealloc次数,提供更好的引用局部性,并且还可以节省一个指针的空间。

从C99开始,您可以在数组成员声明中省略零:

struct queue {
    int a;
    int b;
    q_info array[];
};

标准C从未允许零大小的数组成员(在我看来很不幸);在C99之前,如果想避免未定义行为,就必须使用大小为MAX_QUEUE_SIZE的数组,并分配sizeof(queue)-sizeof(elementType)(MAX_QUEUE_SIZE-num_entries)字节。许多代码使用大小为1并分配sizeof(queue)+sizeof(elementType)(num_entries-1)字节,但有时会分配比所需更多的空间,并且是未定义行为(尽管幸运的是编译器通常会使其表现如预期)。 - supercat

2

以下是完全不同的两个东西:

  • 第一个包含指向一个外部数组的指针。
  • 第二个是一个内联数组,恰好有零个元素。

人们这样做的原因是它更加节省空间。你只需过度分配结构体需要的内存,然后假装数组比声明的元素多 - 编译器不会介意(通常)。

这也意味着你少了一个要解引用的指针,并且你可以为结构体和数组分配和释放内存。

显然,这个技巧只适用于数组是结构体中的最后一个元素时。


1

在第一个中,实际上在struct queue中分配了一个指针,并且sizeof(struct queue) == 2 * sizeof(int) + sizeof(q_info*)

在第二个中,实际上没有指针或任何名为array的东西存在于struct queue中,而sizeof(struct queue) == 2 * sizeof(int)。这被称为一种巧妙地引用数据的技巧,使用array可以方便地在之前或之后引用数据。我在实现内存分配器时使用了这个技巧。


1
对于大小为零的数组成员,当你分配结构体时可以分配比struct queue的大小更多的内存(例如malloc(sizeof(struct queue) + sizeof(q_info) * 10)),以便拥有一个连续的内存区域可供使用。然后该数组将成为已分配内存的一部分,对于上述例子的分配,它包含了十个q_info条目。
对于指针,你需要进行两次分配,一次用于queue结构体,一次用于array成员。当然,你还需要调用两次free函数,一次用于array指针,一次用于结构体。
但是,一旦分配完成,这两者可以被相同地使用。

1
q_info array[0];

由于自动转换,它会衰减为指针。但是,它是不可分配的。你不能说

array = <some address of an object>;

之后。


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