kan*_*wei 38 c scope function unions
当我阅读ISO/IEC 9899:1999(见:6.5.2.3)时,我看到了一个这样的例子(强调我的):
以下不是有效的片段(因为联合类型在函数中不可见
f
):Run Code Online (Sandbox Code Playgroud)struct t1 { int m; }; struct t2 { int m; }; int f(struct t1 * p1, struct t2 * p2) { if (p1->m < 0) p2->m = -p2->m; return p1->m; } int g() { union { struct t1 s1; struct t2 s2; } u; /* ... */ return f(&u.s1, &u.s2); }
我测试时发现没有错误和警告.
我的问题是:为什么这个片段无效?
Sto*_*ica 33
该示例试图预先说明段落1(强调我的):
6.5.2.36
一个特殊的担保是为了简化使用工会提出:如果一个联合包含共享一个公共初始序列几种结构(见下文),而如果联合对象当前包含这些结构中的一个,它被允许检查常见其中任何一个的初始部分都可以看到完整类型的联合声明.如果对应的成员具有一个或多个初始成员的序列的兼容类型(并且对于位字段,具有相同的宽度),则两个结构共享共同的初始序列.
由于f
之前已声明g
,并且未命名的联合类型是本地的g
,因此毫无疑问,联合类型不可见f
.
该示例未显示如何u
初始化,但假设最后写入成员u.s2.m
,该函数具有未定义的行为,因为它检查p1->m
没有共同的初始序列保证生效.
另一方面,如果它u.s1.m
是在函数调用之前最后写入的,那么访问p2->m
是未定义的行为.
请注意,f
它本身并非无效.这是一个非常合理的函数定义.未定义的行为源于传入它&u.s1
和&u.s2
作为参数.这就是导致未定义行为的原因.
1 - 我引用了N1170,C11标准草案.但规范应该是相同的,只需要向上/向下移动一个或两个段落.
RbM*_*bMm 26
下面是严格的别名规则:C(或C++)编译器做出的一个假设是,取消引用指向不同类型对象的指针永远不会引用相同的内存位置(即别名相互作用).
这个功能
int f(struct t1* p1, struct t2* p2);
Run Code Online (Sandbox Code Playgroud)
假设p1 != p2
因为它们正式指向不同的类型.因此,优化者可能认为对此p2->m = -p2->m;
没有影响p1->m
; 它可以先读取p1->m
寄存器的值,将其与0比较,若比较小于0,则p2->m = -p2->m;
最后返回寄存器值不变!
这里的联合是p1 == p2
在二进制级别上进行的唯一方法,因为所有联合成员都具有相同的地址.
另一个例子:
struct t1 { int m; };
struct t2 { int m; };
int f(struct t1* p1, struct t2* p2)
{
if (p1->m < 0) p2->m = -p2->m;
return p1->m;
}
int g()
{
union {
struct t1 s1;
struct t2 s2;
} u;
u.s1.m = -1;
return f(&u.s1, &u.s2);
}
Run Code Online (Sandbox Code Playgroud)
必须g
返回什么?+1
根据常识(我们改变-1到+1 in f
).但是如果我们看一下gcc的生成程序集并进行-O1
优化
f:
cmp DWORD PTR [rdi], 0
js .L3
.L2:
mov eax, DWORD PTR [rdi]
ret
.L3:
neg DWORD PTR [rsi]
jmp .L2
g:
mov eax, 1
ret
Run Code Online (Sandbox Code Playgroud)
到目前为止,所有都是例外.但是当我们尝试时-O2
f:
mov eax, DWORD PTR [rdi]
test eax, eax
js .L4
ret
.L4:
neg DWORD PTR [rsi]
ret
g:
mov eax, -1
ret
Run Code Online (Sandbox Code Playgroud)
返回值现在是硬编码的 -1
这是因为f
在开始高速缓存的值p1->m
在eax
寄存器(mov eax, DWORD PTR [rdi]
)和不重读它后p2->m = -p2->m;
(neg DWORD PTR [rsi]
) -返回eax
不变.
union此处仅用于
union对象的所有非静态数据成员具有相同的地址.结果&u.s1 == &u.s2
.
有人不懂汇编代码,可以在c/c ++中显示如何严格别名影响f代码:
int f(struct t1* p1, struct t2* p2)
{
int a = p1->m;
if (a < 0) p2->m = -p2->m;
return a;
}
Run Code Online (Sandbox Code Playgroud)
编译器缓存p1->m
值在本地var中a
(当然实际上在寄存器中)并返回它,尽管有p2->m = -p2->m;
变化p1->m
.但编译器假设p1
内存不受影响,因为它假设p2
指向另一个不重叠的内存p1
因此,对于不同的编译器和不同的优化级别,相同的源代码可以返回不同的值(-1或+1).这样和未定义的行为