作为我之前的问题的后续,请考虑以下代码:
int f(int *p);
static inline int g0(int *p) {
*p=0;
f(NULL); // might modify *p
return *p==0;
}
int caller0(int *q) { return g0(q); }
在上面,调用
f
后,编译器必须获取 *p
并执行与 0 的比较,因为 p
可能会别名内存也可用于 f
。然而:
static inline int g1(int *restrict p) {
*p=0;
f(NULL); // cannot modify *p
return *p==0;
}
int caller1(int *q) { return g1(q); }
因为
restrict
,编译器可以推断出f
修改*p
是非法的,因此它可以只返回1,从而优化了获取和比较。 Clang 17 做了这个优化。然而:
int caller2(int *q) {
{ int *restrict p=q;
*p=0;
f(NULL);
return *p==0;
}
}
这个
caller2
在我看来相当于caller1
,只是手动内联。同一个 Clang 编译器没有对此进行优化(它发出获取和比较)。
第一个问题:编译器允许优化吗
caller2
?
进一步:
int caller3(int *q) {
... // many lines of code
{ int *restrict p=q;
*p=0;
... // do something useful
f(q);
if (*p==0) { ... } // do something useful
}
... // many lines of code
}
现在这是我真正关心的部分。如果允许编译器优化
caller2
,那么是否允许优化caller3
?
根据我对标准的阅读,
q
在这里并不是“基于”p
,并且*p
是通过限制限定指针修改的,因此编译器可以假设f
不会修改*p
(进一步;f
甚至不允许读取*q
,因此编译器甚至可以在调用*p=0
之后对赋值f
重新排序(尽管f
可以合法地读/写q[1]
)
))。
这是正确的吗?如果是,则表明即使在函数体内也可以创造性地使用
restrict
来向编译器提供有关别名的各种提示。
因为
,编译器可以推断出restrict
修改f
是非法的,因此它可以只返回1,从而优化了获取和比较。*p
“非法”不是正确的术语。该规范并不禁止此类行为。如果
f
确实修改了*p
,它只会放弃这种情况。在这种情况下,对 g0()
的特定调用以及程序的整个执行都具有未定义的行为。因此(现代的解释是)编译器可以生成一个程序,其行为就好像 f
没有修改 *p
一样,无论实际情况如何。
编译器允许优化caller2吗?
如果我们接受编译器可以按照描述优化
caller1()
,那么是的,可以对caller2()
执行类似的优化。但它没有义务这样做。
如果允许编译器优化
,是否允许优化caller2
?caller3
是的,如果我们接受允许对
caller2
进行优化的论点,那么同样的论点允许对 caller3
进行类似的优化。
正如您所观察到的,
q
并不是“基于”p
。由于 p
没有链接,并且其地址和基于 p
的值的另一个对象的地址都不会被获取,因此编译器可以得出结论,f()
无法访问基于 restrict
限定的任何指针p
,因此,如果 f(q)
的评估修改了 *p
,则程序的行为是未定义的。
如果是,则表明
甚至可以在函数内部创造性地使用,以向编译器提供有关别名的各种提示。restrict
我想你可以这样描述它。我更倾向于说“
restrict
可以在函数内部使用来邀请编译器为它们发出损坏的代码”。
在示例中,如果您想让编译器基于
f
不会修改其参数指向的数据的假设进行优化,那么将其参数声明为指向 const
的指针会更干净、更清晰
数据:
int f(const int *p);
如果你做不到这一点,那么你想要允许的非混叠假设可能不安全。
总的来说,
restrict
对于自动变量的使用并没有真正的意义。编译器不需要您的帮助来查看涉及这些的别名机会,并且建议它根据这些更有限的视图做出决策是自找麻烦。