【问题标题】:How does an array of structures with flexible array members behave?具有灵活数组成员的结构数组如何表现?
【发布时间】:2016-03-20 19:36:22
【问题描述】:

正如标题所述,我想知道带有 flexible array member 的 C 结构数组的行为方式。这是一个例子:

struct vector {
    size_t length;
    double array[];
};

维基百科文章说:

此类结构上的sizeof 运算符需要给出灵活数组成员的偏移量。

在我的机器上,这对应于 8 个字节 (sizeof(size_t))。但是,当我执行以下操作时会发生什么:

显然数组不能保存向量v0 的数据,因为它只有3*8 字节= 24 字节 宽。我该如何处理这样的情况?

#define LENGTH 10

int main() {
    struct vector arr[3];

    struct vector *v0 = calloc(1, sizeof(*v0) + LENGTH * sizeof(v0->array[0]));
    v0->length = LENGTH;

    size_t i;
    for (i = 0; i < v0->length; i++) {
        v0->array[i] = (double) i;
    }

    struct vector v1;
    struct vector v2;

    arr[0] = *v0;
    arr[1] =  v1;
    arr[2] =  v2;

    for (i = 0; i < arr[0].length; i++) {
        printf("arr[0].array[%2zu] equals %2.0lf.\n", i, arr[0].array[i]);
        printf("    v0->data[%2zu] equals %2.0lf.\n", i, v0->array[i]);
    }
    return 0;
}

例如,当我正在编写一个库(头文件:mylib.h,源代码:my lib.c)并希望对用户隐藏一个特定结构的实现(头文件中声明的结构,源代码中定义 - 隐藏)。可悲的是,这个结构包含一个灵活的数组成员。当用户尝试创建命名结构数组时,这不会导致意外行为吗?

额外:在OpenSTD C Spec 中阅读有关灵活数组的更多信息。
只需搜索“灵活数组成员”。

编辑:C11 标准的最新草案,C 语言的最新免费参考可在此处获得:http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf

【问题讨论】:

  • “当用户尝试创建命名结构数组时,这不会导致意外行为吗?”如果操作正确,则不会。如果用户不应该知道灵活数组字段,那么显而易见的事情就是让 API 为用户进行分配。
  • @kaylum 一个创建数组并且跟踪索引之类的东西的API?我的意思是索引不适用于不同大小的结构......这将如何工作?在我看来远非显而易见。
  • 该结构已经有一个长度字段。这准确地告诉用户有效的索引是什么。
  • @kaylum 假设我想访问第五个元素。所以我做了类似sizeof (struct mystruct) + array[0].length* sizeof (array[0].data[0]) + sizeof (struct mystruct) + array[1].length* sizeof (array[1].data[1]) + sizeof (struct mystruct) + array[2].length* sizeof (array[2].data[2]) + ...?我想这很好。我很确定这不是正确处理此类结构的方法。避免灵活的数组成员,做一个额外的 malloc 并使用良好的旧式数据指针成员会更有效。
  • 使用arr[i] = ...,您仅将第一个sizeof(size_t) 字节复制到arr[i]

标签: c arrays struct flexible-array-member


【解决方案1】:

以灵活数组作为最后一个成员的结构不能用作其他结构的成员或数组元素。在这种结构中,不能使用灵活数组,因为它的大小为 0 个元素。 Jonathan Leffler 引用的 C 标准是明确的,尽管使用的语言非常技术性,并且通过搜索 flexible 无法在标准中找到段落。

编译器应该为您的结构向量数组发出错误。

在您的程序中,您应该改用指向struct vectors 的指针数组,每个指针指向一个为其灵活数组中适当数量的元素分配的对象。

这是修改后的版本:

#include <stdio.h>
#include <stdlib.h>

struct vector {
    size_t length;
    double array[];
};

struct vector *make_vector(size_t n) {
    struct vector *v = malloc(sizeof(*v) + n * sizeof(v->array[0]));
    v->length = n;
    for (size_t i = 0; i < n; i++) {
        v->array[i] = (double)i;
    }
    return v;
}

int main(void) {
    struct vector *arr[3];

    arr[0] = make_vector(10);
    arr[1] = make_vector(5);
    arr[2] = make_vector(20);

    for (size_t n = 0; n < 3; n++) {
        for (size_t i = 0; i < arr[n]->length; i++) {
            printf("arr[%zu]->array[%2zu] equals %2.0lf.\n",
                   n, i, arr[0]->array[i]);
        }
    }
    return 0;
}

【讨论】:

    【解决方案2】:

    您不能拥有具有灵活数组成员的结构数组。

    C 标准 ISO/IEC 9899:2011 规定:

    6.7.2.1 结构和联合说明符

    ¶3 结构或联合不应包含不完整或函数类型的成员(因此, 结构不应包含自身的实例,但可以包含指向实例的指针 本身),除了具有多个命名成员的结构的最后一个成员 可能有不完整的数组类型;这样的结构(以及任何包含,可能 递归地,作为这种结构的成员)不应是结构的成员或 数组的元素。

    添加了重点——斜体部分禁止具有灵活数组成员的结构数组。不过,您可以拥有指向此类结构的指针数组,但每个结构将被单独分配。

    ¶18 作为一种特殊情况,具有多个命名成员的结构的最后一个元素可能 数组类型不完整;这称为灵活数组成员。在大多数情况下, 灵活的数组成员被忽略。特别是,结构的大小就像 省略了灵活的数组成员,除了它可能有更多的尾随填充 遗漏将意味着。但是,当.(或-&gt;)运算符具有左操作数时 (指向)具有灵活数组成员和正确操作数名称的结构 成员,它的行为就好像该成员被替换为最长的数组(具有相同的 元素类型)不会使结构大于被访问的对象;这 数组的偏移量应保持灵活数组成员的偏移量,即使这会有所不同 从替换阵列的那个。如果这个数组没有元素,它的行为就像 它有一个元素,但如果尝试访问该元素,则行为未定义 元素或生成一个越过它的指针。

    这定义了一个灵活的数组成员。

    如果你仔细想想,这是有道理的。指针算术和数组依赖于数组中所有大小相同的对象(因此a[i] == *(a + i) 等价),因此拥有一个不同大小的对象数组会破坏指针算术。指针数组不是问题,因为指针大小相同,即使指向的对象大小不同。

    如果你设法让编译器忽略违反的约束,那么数组的每个元素都将有一个零长度的灵活数组成员,因为结构将被视为具有没有数组成员的结构的大小(即'在大多数情况下,灵活的数组成员被忽略'规则在起作用)。但是编译器应该拒绝具有灵活数组成员的结构类型的数组;这样的代码违反了约束(¶3 在约束部分;¶18 在语义部分)。

    【讨论】:

    • 狡猾,它小心地避免在 /3 中使用“灵活数组成员”一词,以免在搜索时发现它
    • 是的;它没有在 ¶3 中定义“灵活数组成员”——在 ¶18 中——并且它不使用前向引用(可能是因为它在同一部分中定义)。但是该标准非常明确,具有灵活数组成员的结构不能成为数组的一部分,即使它没有使用该术语。
    猜你喜欢
    • 2020-04-13
    • 1970-01-01
    • 2012-11-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-25
    • 2011-03-04
    • 2019-10-21
    相关资源
    最近更新 更多