6.7.4.2 restrict 的形式化定义
TIP
restrict 的难点不在语法,而在“同一个对象究竟允许通过哪些左值访问”。这一节给的是标准的形式化定义,目的是让优化器可以合法地假设“没有别名”。
基本设定
设
D是一个普通标识符的声明,它使某个对象P成为“指向类型T的restrict限定指针”。标准接着引入一个块
B:- 若
D出现在某个块内部且不是extern,则B就是那个块; - 若
D是函数定义的形参声明之一,则B是对应函数体块; - 其他情形下,
B取程序启动时执行的那个外层块。
- 若
“基于 P” 的含义
某个指针表达式
E被称为基于对象P,意思是:- 在
B的某次执行中,若把P改成指向它原先指向数组对象的一份副本; - 那么
E的值会因此改变。
- 在
这个定义只对指针类型表达式成立。
直观地说,
E是否“基于P”,看的是它是否依赖P自身的值,而不是依赖P间接指向的内容。
核心规则
在
B的每次执行中,设L是任意一个满足&L基于P的左值。如果
L被用来访问它指代的对象X的值,并且X也被修改了,那么必须同时满足:T不能是const限定类型;- 任何其他用来访问
X的左值,其地址也都必须基于P; - 任何修改
X的访问,为本节目的也视为修改了P。
如果把另一个受限指针对象
P2所基于的指针表达式赋给P,那么:- 要么
P2所在块B2的执行先于B开始; - 要么
B2的执行在这次赋值前已经结束。
- 要么
若不满足上述要求,行为未定义。
其他说明
这里所谓“
B的一次执行”,对应的是:一个与B关联、具有自动存储期的标量对象可能拥有的那段生命周期。翻译器可以自由忽略
restrict的部分或全部别名推断含义;也就是说,restrict允许优化,但不强制实现一定做优化。
例子
- 文件作用域下:
c
int * restrict a;
int * restrict b;
extern int c[];1
2
3
2
3
标准想表达的是:如果某个对象经由 a、b、c 之一被访问并且被修改,那么程序不应再通过另外两个名字去访问它。
- 经典形参例子:
c
void f(int n, int * restrict p, int * restrict q)
{
while (n-- > 0)
*p++ = *q++;
}1
2
3
4
5
2
3
4
5
这里承诺的是:在 f 的每次执行中,若某个对象经 p 访问,就不应再同时经 q 访问,反之亦然。
NOTE
restrict 的收益在于:调用点很多时,编译器不必把所有调用都展开分析,也能假定 p 与 q 不别名;代价则是程序员必须自己保证所有调用都满足这个承诺。