我想弄清楚,在C语言中,超级类 struct 是否被大量包含在子类 struct 中,而不仅仅是具有相同成员前缀的子类和超级类.

在下面的示例代码中,我试图阐明我的 idea :

#include "stdlib.h"
#include "stdio.h"
#include "string.h"

enum type {
    IS_A,
    IS_B,
};

struct header {
    enum type type;
};

struct a {
    struct header hdr;
    float x;
};

struct b {
    struct header hdr;
    int y;
};

void do_with_a(struct header *obj) {
    if (obj->type == IS_A) {
        struct a *a = (struct a *)obj;
        printf("%f\n", a->x);
    }
}

void do_with_b(struct header *obj) {
    // Oops forgot to check the type tag
    struct b *b = (struct b *)obj;
    printf("%d\n", b->y);
}

int main() {
    struct a *a = malloc(sizeof(*a));

    a->hdr.type = IS_A;
    a->x = 3.0;

    do_with_a(&a->hdr);
    do_with_b(&a->hdr);
}

我有理由相信,如果用"a"来称呼do_with_b(),它将永远是未定义的行为.My primary question是指do_with_a()是否总是定义为行为(假设我正确设置了类型标记),还是在编译器作者改变主意或改进分析时,这会给自己带来灾难.

作为一个sub-question:我认为将struct a *转换成struct header *&ap->hdr(struct header *)ap都是很明确的定义,是这样吗?

看看C11标准,似乎有两个相关段落,一个在第6.7.2.1节第15段:

在 struct 对象中,非位字段成员和位字段所在的单元的地址按声明顺序递增.指向经过适当转换的 struct 对象的指针指向其初始成员...

6.5第7段中有一个:

对象的存储值只能由具有以下类型之一的左值表达式访问:

  • 与对象的有效类型兼容的类型,
  • ...
  • 在其成员中包含上述类型之一的聚合或联合类型...

在这两者之间,我不清楚这是对标准的预期解释,还是我太有希望了.


我试过用GCC和clang两种语言编译上述代码,它们在优化打开和关闭时的表现似乎都不一样.然而,当设置为-Wstrict-aliasing=1时,GCC确实会发出两个向下转换的警告.该语言有些模糊,表示它"可能"打破严格的别名,其中对该标志的描述表明误报非常常见,因此这是不确定的:

undefined_test.c: In function ‘do_with_a’:
undefined_test.c:26:39: warning: dereferencing type-punned pointer might break strict-aliasing rules [-Wstrict-aliasing]
   26 |                 struct a *a = (struct a *)obj;
      |                                       ^
undefined_test.c: In function ‘do_with_b’:
undefined_test.c:33:31: warning: dereferencing type-punned pointer might break strict-aliasing rules [-Wstrict-aliasing]
   33 |         struct b *b = (struct b *)obj;
      |

相关问题:will casting around sockaddr_storage and sockaddr_in break strict aliasing

这个被接受的答案的最后部分基本上回答了这个问题,但我似乎并不满意.关于它的大多数 comments 似乎主要涉及"公共前缀"的情况,而不是"嵌套 struct "的情况.我不清楚"嵌套 struct "案是否得到了充分辩护.

推荐答案

  • 我的主要问题是,do_with_a()是否总是定义为行为

    作为一个子问题:我认为通过&;ap->;hdr或(struct header*)ap都是定义良好的,是这样吗?

    根据初始成员规则C17 6.7.2.1/15,其定义明确:

    在 struct 对象中,非位字段成员和位字段所在的单位

    这也符合您在6.5§6和§7中引用的有效类型/严格别名规则

  • 我有理由相信,do_with_b()永远都是未定义的行为

    是的,它不是一个兼容的 struct .所以这是一个严格的别名冲突,也可能是一个对齐问题.但是请注意,严格的别名规则与一个名为"公共初始序列"的奇怪规则兼容,在本例中,它允许您判断b的标题部分.C17 6.5.2.3/6:

    为了简化unions 的使用,做出了一项特殊保证:如果unions 包含

    也就是说,如果在翻译单元中添加类似于typedef union { struct a a_; struct b b_; } dummy;的内容,就可以以定义良好的方式判断每个 struct 的标题部分.但这并不是说编译器在实现这一功能时可能会有不稳定的标准遵从性,而且委员会收到了一些关于它的缺陷报告(我不确定它在C23中的状态).

  • 然而,当设置为-Wstrict aliasing=1时,GCC会发出关于这两种向下转换的警告

    gcc中的这些选项的状态介于破碎和非常破碎之间.-fno_strict_aliasing完全禁用它是可靠的.

    严格的别名规则本身有很多缺陷:例如,您分配的内存的有效类型实际上是struct headerfloat,而不是struct a,因为您没有将struct a类型的左值写入malloc返回的内存.类似地,假设我们用malloc分配一个内存块,然后通过在for循环中初始化它,将其视为type的数组,那么实际上我们没有有效的type[]类型,而是单个对象.但如果像这样实现,整个C语言就会崩溃.

C++相关问答推荐

修改pGM使用指针填充2-D数组但不起作用

了解一些CLIPS原语数据类型

为什么可以在typedef之前使用typedef d struct 体?

C lang:当我try 将3个或更多元素写入数组时,出现总线错误

为静态库做准备中的奇怪行为

致命:ThreadSaniizer:在Linux内核6.6+上运行时意外的内存映射

在我的代码中,我需要在哪里编写输出函数?

在C语言中,是否可以使枚举数向后计数?

对重叠字符串使用MemMove

是否可以通过调用两个函数来初始化2D数组?示例:ARRAY[STARTING_ROWS()][STARTING_COLUMNS()]

为什么指针运算会产生错误的结果?

是否需要包括<;errno.h>;才能使用perror?

在vfork()之后,链接器如何在不 destruct 父内存的情况下解析execve()?

安全倒计时循环

在C程序中使用Beaglebone Black UART的问题

在运行时判断C/C++指针是否指向只读内存(在Linux操作系统中)

基于蝶数恰好有8个除数的事实的代码

是否可以在多字 C 类型中的任何位置混合存储和类型限定符?

无法在线程内用 C 打印?

为什么需要struct in_addr