每个版本的标准都将对许多别名结构的支持视为实施质量问题,因为基本上不可能编写支持所有有用结构、不阻止任何有用优化并且可以被所有编译器支持的规则无需大量返工。考虑以下函数:
struct foo {int length; int *dat; };
int test1(struct foo *p)
{
int *ip = &p->length;
*ip = 2;
return p->length;
}
我认为很明显,任何高质量的编译器都应该能够处理类型对象struct foo
可能受到对*ip
. 另一方面,考虑函数:
void test2(struct foo *p)
{
int i;
for (i=0; i < p->length; i++)
p->dat[i] = 0;
}
是否应该要求编译器考虑到写入p->dat[i]
可能影响 的值的可能性p->length
,例如通过p->length
至少在循环的第一次迭代之后重新加载 的值?
我认为委员会的一些成员可能打算要求编译器做出这样的允许,但我不认为他们都这样做了,而且所写的规则也不需要它,因为它们列出了可能用于的左值类型访问类型的对象struct foo
,并且int
不在其中。有些人可能认为遗漏是偶然的,但我认为这是基于编译器将规则解释为要求在某些上下文中作为某种特定类型访问的对象由具有可见关联的左值访问的期望在该上下文中具有所列类型之一的对象。什么构成“可见关联”的问题作为 QoI 问题留在了标准的管辖范围之外,但编译器编写者应该在可行时做出合理的努力来识别关联。
在类似的函数test1
中,类型的左值p
用于派生ip
,并且p
不以任何其他方式用于在p->length
形成ip
和最后一次使用之间进行访问。因此,编译器应该可以毫不费力地认识到,即使没有一般规则允许使用类型指针访问不相关结构的成员,*ip
也无法在以后的 read to 中重新排序存储。但是,在 内部,没有可见的方法可以将 的地址用于计算指针,因此优化旨在用于最常见目的的编译器来提升对 的读取是合理的。p->length
int*
int
test2
p->length
p->dat
p->length
在循环之前期望它的值不会改变。
clang 和 gcc 并没有做出任何努力来识别派生指针的对象的类型,而是选择表现得好像标准授予了使用其类型指针访问 struct(但不是 union!)成员的一般权限。这是允许的,但不是标准所要求的(符合标准但垃圾质量的实现可以test1
以任意无意义的方式处理),但是对指针派生的盲目性不必要地限制了程序员可用的构造范围,并且有必要放弃应该有用的东西优化,例如test2()
.
总的来说,几乎所有与 C 中的别名相关的问题的正确答案都是“这是一个实现质量问题”。观察 clang 和 gcc 的作用可能对需要安抚-fstrict-aliasing
这些编译器模式的人有用,但与标准的实际内容无关。