鉴于代码:
struct s1 {unsigned short x;}; struct s2 {unsigned short x;}; union s1s2 { struct s1 v1; struct s2 v2; }; static int read_s1x(struct s1 *p) { return p->x; } static void write_s2x(struct s2 *p, int v) { p->x=v;} int test(union s1s2 *p1, union s1s2 *p2, union s1s2 *p3) { if (read_s1x(&p1->v1)) { unsigned short temp; temp = p3->v1.x; p3->v2.x = temp; write_s2x(&p2->v2,1234); temp = p3->v2.x; p3->v1.x = temp; } return read_s1x(&p1->v1); } int test2(int x) { union s1s2 q[2]; q->v1.x = 4321; return test(q,q+x,q+x); } #include int main(void) { printf("%dn",test2(0)); }
整个程序中存在一个联合对象 – q
。 其活动成员设置为v1
,然后设置为v2
,然后再设置为v1
。 代码仅在q.v1
上使用address-of运算符,或者在该成员处于活动状态时使用结果指针,同样使用q.v2
。 由于p1
, p2
和p3
都是相同类型,因此使用p3->v1
访问p1->v1
和p3->v2
访问p2->v2
应该是完全合法的。
我没有看到任何可以certificate编译器无法输出1234的东西,但是包括clang和gcc在内的许多编译器生成的代码输出4321.我认为正在发生的是他们认为p3上的操作实际上不会改变内容对于内存中的任何位,它们可以完全被忽略,但我没有在标准中看到任何可以忽略p3
用于将数据从p1->v1
复制到p2->v2
而反之亦然的p2->v2
。
标准中是否有任何可以certificate这种行为的理由,或者编译器是否只是不遵循它?
我相信你的代码是一致的,并且GCC和Clang的-fstrict-aliasing
模式存在缺陷。
我找不到C标准的正确部分,但是在我为C ++模式编译代码时会出现同样的问题,我确实找到了C ++标准的相关段落。
在C ++标准中,[class.union] / 5定义了当union =
用于union访问表达式时会发生什么。 C ++标准规定,当内联operator =
的成员访问表达式中涉及union时,union的活动成员将更改为表达式中涉及的成员(如果该类型具有一个简单的构造函数,但是因为这是C代码,它确实有一个简单的构造函数)。
请注意, write_s2x
无法更改union的活动成员,因为赋值表达式中不包含union。 您的代码不会认为发生这种情况,所以没关系。
即使我使用placement new
来显式更改哪个union成员处于活动状态,这应该是编译器提示活动成员发生了变化,GCC仍会生成输出4321
代码。
这看起来像GCC和Clang的错误,假设活动联合成员的切换不能在这里发生,因为他们无法识别p1
, p2
和p3
都指向同一个对象的可能性。
GCC和Clang(以及几乎所有其他编译器)都支持C / C ++扩展,您可以在其中读取union的非活动成员(获取任何可能的垃圾值),但前提是您在成员访问中执行此访问涉及工会的表达。 如果 v1
不是活动成员,则read_s1x
将不会在此特定于实现的规则下定义行为,因为联合不在成员访问表达式中。 但由于v1
是活跃成员,这应该无关紧要。
这是一个复杂的案例,我希望我的分析是正确的,因为他不是编译器维护者或其中一个委员会的成员。
通过对标准的严格解释,此代码可能不符合要求 。 让我们关注着名的§6.5p7的文本:
对象的存储值只能由具有以下类型之一的左值表达式访问:
– 与对象的有效类型兼容的类型,
– 与对象的有效类型兼容的类型的限定版本,
– 对应于对象的有效类型的有符号或无符号类型,
– 对应于对象有效类型的限定版本的有符号或无符号类型,
– 聚合或联合类型,其成员中包含上述类型之一(包括递归地,子聚合或包含联合的成员),或者
– 一个字符类型。
(强调我的)
你的函数read_s1x()
和write_s2x()
与我在整个代码的上下文中标记为粗体的函数相反 。 只有这一段,您可以得出结论,这是不允许的:指向union s1s2
的指针将被允许别名指向struct s1
的指针,但反之亦然。
如果您在test()
手动“内联”这些函数,那么这种解释当然意味着代码必须按预期工作。 对于i686-w64-mingw32
,gcc 6.2就是这种情况。
增加两个论点,支持上述严格的解释:
我没有阅读标准,但在严格别名模式下使用指针(即使用-fstrict-alising
)是危险的。 请参阅gcc在线文档 :
特别注意这样的代码:
union a_union { int i; double d; }; int f() { union a_union t; td = 3.0; return ti; }
从不同的工会成员那里读取的做法比最近写的
type-punning
(称为type-punning
)很常见。 即使使用-fstrict-aliasing
,也允许使用类型 – 双关语,前提是通过联合类型访问内存。 因此,上面的代码按预期工作。 请参阅结构联合枚举和位字段实现。 但是,此代码可能不会:
int f() { union a_union t; int* ip; td = 3.0; ip = &t.i; return *ip; }
类似地,通过获取地址,转换结果指针和取消引用结果的访问具有未定义的行为,即使转换使用联合类型,例如:
int f() { double d = 3.0; return ((union a_union *) &d)->i; }
-fstrict-aliasing
选项在级别-O2,-O3,-Os处启用。
在第二个例子中找到了类似的东西吧?
它不是符合或不符合 – 它是优化“陷阱”之一。 您的所有数据结构都已经过优化,并且您将相同的指针传递给优化的输出数据,因此执行树将简化为值的简单printf。
sub rsp, 8 mov esi, 4321 mov edi, OFFSET FLAT:.LC0 xor eax, eax call printf xor eax, eax add rsp, 8 ret
要改变它你需要使这个“转移”function容易产生副作用并强制实际分配。 它将强制优化器不减少执行树中的那些节点:
int test(union s1s2 *p1, union s1s2 *p2, volatile union s1s2 *p3) /* ....*/ main: sub rsp, 8 mov esi, 1234 mov edi, OFFSET FLAT:.LC0 xor eax, eax call printf xor eax, eax add rsp, 8 ret
这是一个非常简单的测试,只是人工制造了一点点复杂。
需要了解更多c/c++开发分享这种工会的使用是否严格符合?,也可以关注C/ C++技术分享栏目—计算机技术网(www.ctvol.com)!
以上就是c/c++开发分享这种工会的使用是否严格符合?相关内容,想了解更多C/C++开发(异常处理)及C/C++游戏开发关注计算机技术网(www.ctvol.com)!)。
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/c-cdevelopment/980554.html