据说零长度数组是可变长度结构,我可以理解。 但是让我困惑的是为什么我们不使用一个指针,我们可以用相同的方式解引用和分配不同的大小结构。
编辑 – 添加评论的例子
假设:
struct p { char ch; int *arr; };
我们可以使用这个:
struct p *p = malloc(sizeof(*p) + (sizeof(int) * n)); p->arr = (struct p*)(p + 1);
获得连续的内存块。 但是,我似乎忘记了空间占用空间,它似乎是零大小的数组方法不同的事情。
这些是comp.lang.c常见问题2.6中讨论的所谓“struct hack”的各种形式。
定义一个大小为0的数组在C中实际上是非法的,至少自从1989年的ANSI标准以来。 一些编译器允许它作为扩展,但是依赖于它导致不可移植的代码。
一个更便携的方法来实现这个是使用长度为1的数组,例如:
struct foo { size_t len; char str[1]; };
你可以分配多于sizeof (struct foo)
字节,用len
来跟踪分配的大小,然后访问str[N]
来得到数组的第N个元素。 由于C编译器通常不会进行数组边界检查,因此通常会“工作”。 但严格来说,行为是不确定的。
1999年的ISO标准增加了一个名为“灵活的阵列成员”的功能,旨在取代这种用法:
struct foo { size_t len; char str[]; };
您可以像旧的struct hack一样处理这些问题,但是行为已经很好的定义了。 但是你必须自己做所有的簿记。 sizeof (struct foo)
仍然不包含数组的大小。
当然,你可以使用一个指针来代替:
struct bar { size_t len; char *ptr; };
这是一个非常好的方法,但它有不同的语义。 “struct hack”或灵活数组成员的主要优点是数组与结构的其余部分连续分配,并且可以使用memcpy
将结构复制到结构中(只要目标已经正确分配)。 用一个指针,数组是分开分配的 – 这可能是也可能不是你想要的。
如果使用指针,则结构将不再具有可变长度:它将具有固定的长度,但其数据将存储在不同的位置。
零长度数组背后的想法是将数组中的数据与结构中的其余数据一起存储,以便数组的数据遵循存储器中的结构数据。 指向一个单独分配的内存区域不会让你这样做。
*这样的阵列也被称为灵活阵列 ; 在C99中,您将它们声明为element_type flexArray[]
而不是element_type flexArray[0]
,即您将其删除为零。
指针并不是真的需要,所以它的成本空间没有任何好处。 另外,这可能意味着另一个层面的间接,这也不是真的需要。
比较这些示例声明,对于动态整数数组:
typedef struct { size_t length; int data[0]; } IntArray1;
和:
typedef struct { size_t length; int *data; } IntArray2;
基本上,指针表示“数组的第一个元素是在这个地址,可以是任何东西”比通常需要的更通用。 所需的模型是“数组的第一个元素就在这里,但我不知道数组有多大”。
当然,第二种形式可以增长数组,而不用担心“基本”地址( IntArray2
结构本身的地址)发生变化,这可能是非常简单的。 你不能用IntArray1
来做到这IntArray1
,因为你需要一起分配基本结构和整数数据元素。 权衡,权衡…
这是因为使用指针需要单独分配和分配。
struct WithPointer { int someOtherField; ... int* array; }; struct WithArray { int someOtherField; ... int array[1]; };
要获得WithPointer
的“对象”,您需要执行以下操作:
struct WithPointer* withPointer = malloc(sizeof(struct WithPointer)); withPointer.array = malloc(ARRAY_SIZE * sizeof(int));
获取WithArray的“对象”:
struct WithArray* withArray = malloc(sizeof(struct WithArray) + (ARRAY_SIZE - 1) * sizeof(int));
而已。
在某些情况下,将阵列连续存储也是非常方便的,甚至是必要的。 例如在网络协议包中。