通过在一个结构之间转换指针来实现 C 中的继承是否合法,该结构是另一个结构的子集而不是第一个成员?

Is it legal to implement inheritance in C by casting pointers between one struct that is a subset of another rather than first member?

现在我知道我可以通过将指向 struct 的指针转换为此 struct 的第一个成员的类型来实现继承。

然而,纯粹作为一种学习经验,我开始想知道是否有可能以稍微不同的方式实现继承。

此代码合法吗?

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

struct base
{
    double some;
    char space_for_subclasses[];
};

struct derived
{
    double some;
    int value;
};

int main(void) {
    struct base *b = malloc(sizeof(struct derived));
    b->some = 123.456;
    struct derived *d = (struct derived*)(b);
    d->value = 4;
    struct base *bb = (struct base*)(d);
    printf("%f\t%f\t%d\n", d->some, bb->some, d->value);
    return 0;
}

This code seems to produce desired results ,但据我们所知,这远不能证明它不是 UB。

我怀疑这样的代码可能合法的原因是我看不到这里可能出现的任何对齐问题。但是当然这远不是​​知道没有出现这样的问题,即使确实没有对齐问题,代码也可能由于任何其他原因仍然是 UB。

当我阅读标准第 §6.2.6.1/P5 章时,

Certain object representations need not represent a value of the object type. If the stored value of an object has such a representation and is read by an lvalue expression that does not have character type, the behavior is undefined. [...]

所以,只要 space_for_subclasses 是一个 char (array-decays-to-pointer) 成员并且你用它来读取值,你应该没问题。


说的就是回答

Is char space_for_subclasses[]; necessary?

是的,

引用§6.7.2.1/P18,

As a special case, the last element of a structure with more than one named member may have an incomplete array type; this is called a flexible array member. In most situations, the flexible array member is ignored. In particular, the size of the structure is as if the flexible array member were omitted except that it may have more trailing padding than the omission would imply. However, when a . (or ->) operator has a left operand that is (a pointer to) a structure with a flexible array member and the right operand names that member, it behaves as if that member were replaced with the longest array (with the same element type) that would not make the structure larger than the object being accessed; the offset of the array shall remain that of the flexible array member, even if this would differ from that of the replacement array. If this array would have no elements, it behaves as if it had one element but the behavior is undefined if any attempt is made to access that element or to generate a pointer one past it.

删除它,您将访问无效内存,从而导致 undefined behavior。但是,在您的情况下(第二个片段),您无论如何都不会访问 value,因此这不会成为问题此处

这或多或少与 struct sockaddr 使用的穷人继承相同,并且 当前一代编译器可靠。演示问题的最简单方法是这样的:

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

struct base
{
    double some;
    char space_for_subclasses[];
};
struct derived
{
    double some;
    int value;
};

double test(struct base *a, struct derived *b)
{
    a->some = 1.0;
    b->some = 2.0;
    return a->some;
}

int main(void)
{
    void *block = malloc(sizeof(struct derived));
    if (!block) {
        perror("malloc");
        return 1;
    }
    double x = test(block, block);
    printf("x=%g some=%g\n", x, *(double *)block);
    return 0;
}

如果标准的字母允许 a->someb->some 是同一个对象,则此程序将需要打印 x=2.0 some=2.0,但对于某些编译器和在某些情况下(它不会在所有优化级别发生,您可能必须将 test 移动到它自己的文件)它 打印 x=1.0 some=2.0 代替。

标准的字母是否允许a->someb->some是同一个对象是有争议的。请参阅 http://blog.regehr.org/archives/1466 及其链接的论文。