下面,我将参考问题中链接到的 Sun 论文中的用例。
(相对)明显的案例是 mem_copy() 案例,它属于 Sun 论文中的第二个用例类别(f1() 函数)。假设我们有以下两个实现:
void mem_copy_1(void * restrict s1, const void * restrict s2, size_t n);
void mem_copy_2(void * s1, const void * s2, size_t n);
因为我们知道 s1 和 s2 指向的两个数组之间没有重叠,所以第一个函数的代码很简单:
void mem_copy_1(void * restrict s1, const void * restrict s2, size_t n)
{
// naively copy array s2 to array s1.
for (int i=0; i<n; i++)
s1[i] = s2[i];
return;
}
s2 = '....................1234567890abcde' <- s2 before the naive copy
s1 = '1234567890abcde....................' <- s1 after the naive copy
s2 = '....................1234567890abcde' <- s2 after the naive copy
OTOH,在第二个功能中,可能有重叠。在这种情况下,我们需要检查源数组是否位于目标数组之前或相反,并相应地选择循环索引边界。
例如,说s1 = 100 和s2 = 105。然后,如果n=15,在复制后新复制的s1 数组将超出源s2 数组的前10 个字节。我们需要确保先复制低字节。
s2 = '.....1234567890abcde' <- s2 before the naive copy
s1 = '1234567890abcde.....' <- s1 after the naive copy
s2 = '.....67890abcdeabcde' <- s2 after the naive copy
但是,如果 s1 = 105 和 s2 = 100,那么首先写入低字节将超出源 s2 的最后 10 个字节,我们最终会得到一个错误的副本。
s2 = '1234567890abcde.....' <- s2 before the naive copy
s1 = '.....123451234512345' <- s1 after the naive copy - not what we wanted
s2 = '123451234512345.....' <- s2 after the naive copy
在这种情况下,我们需要先复制数组的最后一个字节,可能会倒退。代码如下所示:
void mem_copy_2(void *s1, const void *s2, size_t n)
{
if (((unsigned) s1) < ((unsigned) s2))
for (int i=0; i<n; i++)
s1[i] = s2[i];
else
for (int i=(n-1); i>=0; i--)
s1[i] = s2[i];
return;
}
很容易看出restrict 修饰符如何提供更好的速度优化机会、消除额外代码和 if-else 决策。
同时,这种情况对粗心的程序员来说是危险的,他们将重叠的数组传递给restrict-ed 函数。在这种情况下,没有任何保护措施可以确保正确复制数组。根据编译器选择的优化路径,结果是不确定的。
第一个用例(init() 函数)可以看作是第二个用例的变体,如上所述。在这里,通过一个动态内存分配调用创建了两个数组。
将两个指针指定为restrict-ed 可以进行优化,否则指令顺序会很重要。例如,如果我们有代码:
a1[5] = 4;
a2[3] = 8;
如果优化器认为有用,它可以重新排序这些语句。
OTOH,如果指针不是restrict-ed,那么重要的是第一个分配将在第二个之前执行。这是因为a1[5] 和a2[3] 有可能实际上是同一个内存位置!很容易看出,如果是这种情况,那么结束值应该是 8。如果我们重新排列指令,那么结束值将是 4!
同样,如果给这个restrict-ed 假定代码提供了非不相交的指针,则结果是未定义的。