JEv*_*ich 5 c struct strict-aliasing language-lawyer
我试图弄清楚是否在 C 中进行模拟子类化,其中超类结构批量包含在子类结构中,而不仅仅是具有相同成员前缀的子类和超类。
\n在下面的示例代码中,我试图阐明我的想法:
\n#include "stdlib.h"\n#include "stdio.h"\n#include "string.h"\n\nenum type {\n IS_A,\n IS_B,\n};\n\nstruct header {\n enum type type;\n};\n\nstruct a {\n struct header hdr;\n float x;\n};\n\nstruct b {\n struct header hdr;\n int y;\n};\n\nvoid do_with_a(struct header *obj) {\n if (obj->type == IS_A) {\n struct a *a = (struct a *)obj;\n printf("%f\\n", a->x);\n }\n}\n\nvoid do_with_b(struct header *obj) {\n // Oops forgot to check the type tag\n struct b *b = (struct b *)obj;\n printf("%d\\n", b->y);\n}\n\nint main() {\n struct a *a = malloc(sizeof(*a));\n\n a->hdr.type = IS_A;\n a->x = 3.0;\n\n do_with_a(&a->hdr);\n do_with_b(&a->hdr);\n}\nRun Code Online (Sandbox Code Playgroud)\n我相当确定,do_with_b()如果使用“a”调用,则始终是未定义的行为。我的主要问题是,是否do_with_a()始终定义行为(假设我已经正确设置了类型标记),或者当编译器作者改变主意或改进他们的分析时,这是否会让自己陷入灾难。
作为一个子问题:我相信通过or将 an 转换struct a *为 a都是明确定义的,是这种情况吗?struct header *&ap->hdr(struct header *)ap
查看C11标准,似乎有两个相关段落,其中一个在第6.7.2.1节第15段中:
\n\n\n在结构体对象中,非位域成员和位域所在的单元的地址按照它们声明的顺序递增。指向结构对象的指针,经过适当转换后,指向其初始成员......
\n
6.5 第 7 段中的一项:
\n\n\n对象的存储值只能由具有以下类型之一的左值表达式访问:
\n\n
\n- 与对象的有效类型兼容的类型,
\n- ...
\n- 聚合或联合类型,其成员中包括上述类型之一......
\n
在这些之间,我不清楚这是否是该标准的预期解释,或者我是否抱有太大希望。
\n我已经尝试过在 GCC 和 clang 中编译的上述代码,并且在打开和关闭优化时似乎都没有不同的行为。然而,当设置为 时,GCC 确实会发出有关两种向下转换的警告信号-Wstrict-aliasing=1。该语言有点模糊,说它“可能”破坏严格的别名,其中该标志的描述表明误报非常常见,因此这是不确定的:
undefined_test.c: In function \xe2\x80\x98do_with_a\xe2\x80\x99:\nundefined_test.c:26:39: warning: dereferencing type-punned pointer might break strict-aliasing rules [-Wstrict-aliasing]\n 26 | struct a *a = (struct a *)obj;\n | ^\nundefined_test.c: In function \xe2\x80\x98do_with_b\xe2\x80\x99:\nundefined_test.c:33:31: warning: dereferencing type-punned pointer might break strict-aliasing rules [-Wstrict-aliasing]\n 33 | struct b *b = (struct b *)obj;\n |\nRun Code Online (Sandbox Code Playgroud)\n相关问题:将 sockaddr_storage 和 sockaddr_in 周围的转换打破严格别名
\n接受的答案的最后几乎回答了问题,但对我来说似乎并不令人满意。大多数评论似乎主要涉及“common-prefix”的情况,而不是“nested-struct”的情况。我不清楚“嵌套结构”案件是否得到了充分的辩护。
\n\n\n我的主要问题是 do_with_a() 是否始终是定义的行为
\n作为一个子问题:我相信通过 &ap->hdr 或 (struct header *)ap 将 struct a * 转换为 struct header * 都是明确定义的,是这种情况吗?
\n
它根据初始成员规则 C17 6.7.2.1/15 进行了明确定义,重点是:
\n\n\n在结构体对象中,非位域成员和位域所在的单元的地址\n按照它们声明的顺序递增。指向结构对象的指针经过适当转换后,指向其初始成员(或者如果该成员是位域,则指向它所在的单元),反之亦然。结构对象内可能有未命名的\n填充,但在其开头没有。
\n
这也与您在 6.5 \xc2\xa76 和 \xc2\xa77 中引用的有效类型/严格别名规则一致
\n\n\n我相当确定 do_with_b() 始终是未定义的行为
\n
是的,它不是一个兼容的结构。所以这是一个严格的别名冲突,也可能是一个对齐问题。但请注意,严格的别名规则与称为“公共初始序列”的奇怪规则兼容,在这种情况下,该规则允许您检查b. C17 6.5.2.3/6:
\n\n为了简化联合的使用,做出了一项特殊保证:如果联合包含共享公共初始序列的多个结构(见下文),并且如果联合对象当前包含这些结构之一,则允许检查它们中任何一个的公共\n初始部分,只要联合的完整类型的声明可见。两个结构共享一个共同的初始序列具有兼容的类型(并且,对于位字段,具有相同的宽度),则两个结构共享一个公共的初始序列。
\n
也就是说,如果您typedef union { struct a a_; struct b b_; } dummy;向翻译单元添加类似的内容,则将允许您以明确定义的方式检查每个结构的标头部分。但这并不是说编译器在实现此功能时可能具有不稳定的标准合规性,并且向委员会提交了一些有关它的缺陷报告(我不确定其根据 C23 的状态)。
\n\n然而,当设置为 -Wstrict-aliasing=1 时,GCC 确实会发出有关两种向下转换的警告信号
\n
gcc 中的这些选项的状态介于损坏和非常损坏之间。-fno_strict_aliasing然而,完全禁用它是可靠的。
严格的别名规则本身有很多缺陷:例如,您分配的内存的有效类型实际上是 astruct header和 a float,而不是 a struct a,因为您没有将类型的左值写入struct a由 返回的内存中malloc。类似地,假设我们分配一块内存,malloc然后通过在 for 循环中初始化它来将其视为数组type,那么我们实际上没有有效类型type[],而是单个对象。但如果这样实现,整个 C 语言就会崩溃。