【问题标题】:Is it legal to implement inheritance in C by casting pointers between one struct that is a subset of another rather than first member?通过在一个结构之间转换指针来实现继承是否合法,该结构是另一个结构的子集而不是第一个成员?
【发布时间】:2017-05-20 05:36:31
【问题描述】:

现在我知道我可以通过将指向 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。

【问题讨论】:

  • char space_for_subclasses[] 行是一条红鲱鱼;它不会改变基础结构的大小
  • @PaulStelian 也许不是,但这样做可能会使malloc(sizeof(struct derived)) 合法化
  • 否则代码在某种意义上是有效的,只要你足够小心在所有情况下正确复制真实类型并且永远不会将基数解释为派生。
  • Malloc 也将始终返回对齐的内存
  • 请注意char space_for_subclasses[] 不需要与int value 具有相同的对齐要求...

标签: c inheritance language-lawyer flexible-array-member pointer-conversion


【解决方案1】:

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

某些对象表示不需要表示对象类型的值。如果存储 对象的值具有这样的表示,并由 执行的左值表达式读取 没有字符类型,行为未定义。 [...]

所以,只要 space_for_subclasseschar (array-decays-to-pointer) 成员并且您使用它来读取值,就应该没问题。


说是回答

char space_for_subclasses[]; 有必要吗?

是的,它

引用 §6.7.2.1/P18,

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

删除它,您将访问无效内存,导致undefined behavior。但是,在您的情况下(第二个 sn-p),您无论如何都不会访问 value,所以这不会成为问题这里

【讨论】:

    【解决方案2】:

    这或多或少与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-&gt;someb-&gt;some 是同一个对象,则该程序将需要打印x=2.0 some=2.0,但在某些编译器和某些条件下(不会发生在所有优化级别,您可能必须将test 移动到它自己的文件中)它打印x=1.0 some=2.0

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

    【讨论】:

    • 是的,我正在阅读提到 struct sockaddrstruct sockaddr_in 等的手册页……这些手册页启发了我发布这个问题 :)
    • 你有没有设法得到1.0 2.0,如果有,在什么条件下?因为如果这种技术如此不可靠,那么 POSIX 如何确保使用 struct sockaddr_in 不会在 UB 中结束?
    • 我问它的原因是我希望既然 POSIX 可以使用这个可怜人的继承,那么也许我也可以使用它,至少在 POSIX 上......所以,POSIX 是否放了任何在 C 之上的更多保证会使其更可靠?
    • wandbox.org/permlink/XroTfKL1XOrkQqw8 好的,得到1.0 2.0。感谢您向我指出这一点。在这种情况下,我如何使用 POSIX 的网络设施,这需要使用struct sockaddr_in,而不触发 UB,对我来说仍然是一个谜。我应该将-fno-strict-aliasing 放在任何使用套接字的代码中吗?不过,这可能值得提出一个不同的问题。
    • @gaazkam 这个问题已经被问过好几次sockaddr。简短的实用答案是“使用 getaddrinfo 并将它返回的 sockaddr 对象视为不透明的”,而语言律师的简短回答是,这是 POSIX 和 C 之间的冲突,尚未得到任何人满意的解决。跨度>
    猜你喜欢
    • 1970-01-01
    • 2023-03-16
    • 1970-01-01
    • 2017-03-15
    • 1970-01-01
    • 2016-09-24
    • 2011-04-20
    • 2011-11-10
    • 2015-03-11
    相关资源
    最近更新 更多