【发布时间】:2019-02-20 07:41:35
【问题描述】:
在 C 语言中,我们不能使用具有与该对象的有效类型不兼容的类型的左值表达式访问对象,因为这会产生未定义的行为。并且基于这一事实,严格的别名规则指出,如果两个指针具有不兼容的类型,则它们不能相互别名(引用内存中的同一个对象)。但在 C11 标准的 p6.2.4 中,允许使用带符号版本左值访问无符号有效类型,反之亦然。
由于最后一段,int *a 和unsigned *b 两个指针可能互为别名,其中一个指针指向的对象值的变化可能导致另一个指针指向的对象的值变化(因为是同一个对象)。
让我们在编译器级别进行演示:
int f (int *a, unsigned *b)
{
*a = 1;
*b = 2;
return *a;
}
在 GCC 6.3.0 上使用 -O2 生成的上述函数的程序集如下所示:
0000000000000000 <f>:
0: movl $0x1,(%rdi)
6: movl $0x2,(%rsi)
c: mov (%rdi),%eax
e: retq
这是相当预期的,因为GCC没有优化返回值,在写入*b之后仍然会再次读取*a的值(因为*b的变化可能会导致*a的变化) .
但是有了这个其他功能:
int ga;
unsigned gb;
int *g (int **a, unsigned **b)
{
*a = &ga;
*b = &gb;
return *a;
}
生成的程序集相当令人惊讶(GCC -O2):
0000000000000010 <g>:
10: lea 0x0(%rip),%rax # 17 <g+0x7>
17: lea 0x0(%rip),%rdx # 1e <g+0xe>
1e: mov %rax,(%rdi)
21: mov %rdx,(%rsi)
24: retq
返回值经过优化,写入*b后不再读取。我知道 int *a 和 unsigned *b 不是兼容的类型,但是 P6.2.4 段中的规则呢(允许使用带符号版本左值访问无符号有效类型,反之亦然)?为什么它不适用于这种情况?为什么编译器会在这种情况下进行这种优化?
关于兼容类型和严格别名的整个故事,我有些不明白。有人可以启发我们吗? (请解释为什么两个指针有不兼容的类型但可以相互别名,想想int *a 和unsigned *b)。
【问题讨论】:
-
unsigned *不是无符号类型。 -
在第一个示例中,
b和unsigned不起作用。同样在第二个更复杂的例子中。 “指针别名”在哪里? -
@WeatherVane 你什么意思,
b不参与?*b的赋值是 asm 代码为返回值重新加载*a的唯一原因,这是因为别名。 -
@melpomene 什么别名?
-
请阅读What is the strict aliasing rule? 中的示例,这可能有助于更好地阐明这些概念。
标签: c language-lawyer compiler-optimization undefined-behavior strict-aliasing