首页 > 解决方案 > 从具有灵活数组成员的结构转换为没有其他相同成员的结构是否是未定义的行为?

问题描述

我想要一个可变大小的结构,但我想将具有一定大小的结构实例嵌入到另一个结构中。这是想法:

struct grid {
    size_t width, height;
    int items[ /* width * height */ ];
};

struct grid_1x1 {
    size_t width, height;
    int items[1];
};

struct grid_holder {
    struct grid_1x1 a, b;
};

int main(void)
{
    struct grid_holder h = {
        .a = { .width = 1, .height = 1, .items = { 0 } },
        .b = { .width = 1, .height = 1, .items = { 0 } },
    };
    struct grid *a = (struct grid *)&h.a, *b = (struct grid *)&h.b;
}

如果我所有的代码都假设 的items成员struct grid具有width * height元素,那么可以ab上面那样进行强制转换吗?

换句话说,考虑到结构在其他方面相同,具有一个元素的灵活数组成员是否始终具有与具有一个元素的固定大小数组成员相同的偏移量和大小?我想要一个基于 C99 标准的答案。如果偏移量可能不同,是否有另一种方法可以实现我在开始时提出的目标?

标签: cstructc99

解决方案


是的,行为不是由 C 标准定义的。

C 2018 6.5 7 或 C 1999 6.5 7 中关于哪些类型可用于访问对象的规则不仅仅是关于对象的布局和表示方式。所以问题中的句子“换句话说,一个元素的灵活数组成员是否总是与一个元素的固定大小的数组成员具有相同的偏移量和大小,假设结构在其他方面是相同的?” 是不正确的。具有相同的偏移量和大小,即使具有相同的结构定义,也不会使结构与别名兼容。

不同的结构是故意不同的类型。考虑这两种类型:

typedef struct { double real, imaginary; } Complex;
typedef struct { double x, y; } Coordinates;

这些结构具有相同的定义(除了成员名称,但即使它们的名称相同,以下内容也成立),但根据 C 标准,它们是不同且不兼容的类型。这意味着在例程中,例如:

double foo(Complex *a, Coordinates *b)
{
    a->real = 3; a->imaginary = 4;
    b->x = 5; b->y = 6;
    return sqrt(a->real*a->real + a->imaginary*a->imaginary);
}

允许编译器优化最后一条语句,因为return 5;b->x = 5; b->y = 6;不能改变a,因为a并且b不能指向同一个对象,或者,如果它们是,b->x = 5; b->y = 6;则未定义的行为。

因此,关于别名的 C 规则是关于兼容类型以及特定情况下的各种例外。它们主要不是关于结构的布局方式。

与上面具有不同但定义相同的结构的示例相比,当我们有多个指向相同结构类型的指针时,编译器不能假定a并且b不是同一对象的别名(不同名称)。在:

double foo(Complex *a, Complex *b)
{
    a->real = 3; a->imaginary = 4;
    b->real = 5; b->imaginary = 6;
    return sqrt(a->real*a->real + a->imaginary*a->imaginary);
}

编译器不能假定返回值为 5,因为a并且b可能指向同一个对象,在这种情况下b->real = 5; b->imaginary = 6;会更改a.


推荐阅读