restrict关键字的行为在c99中由6.7.3.1定义:
设d为提供方法的普通标识符的声明
把一个对象P指定为T类型的限定指针。
如果d出现在块中,并且没有外部存储类,
让b表示块。如果参数列表中出现d
函数定义的声明,让b表示
封锁。否则,让b表示main的块(或
在程序启动时调用的任何函数
环境)。
在下面的内容中,指针表达式e被称为基于对象
p if(在
e)修改p以指向数组对象的副本的求值
它先前指出的将改变E.119)注的价值
仅为具有指针类型的表达式定义了“基于”。
在每次执行b时,让l是基于
p.如果l用于访问对象x的值,则
指定,x也被修改(通过任何方式),然后
适用要求:t不应为const-qualified。每一个左值
用于访问x值的地址也应基于p。
修改x的每个访问也应被视为修改p,因为
本款的目的。如果p被赋予a的值
基于另一个受限指针的指针表达式e
对象p2,与块b2关联,然后执行b2
应在执行B之前开始,或在执行B2之前
作业前结束。如果不满足这些要求,那么
该行为未定义。
就像其他人一样,我很难理解这个定义的所有复杂性。作为对这个问题的回答,我想看一组很好的例子,对于第4段中的每一个要求,都是违反要求的用法。本文:
http://web.archive.org/web/20120225055041/http://developers.sun.com/solaris/articles/cc_restrict.html
很好地用“编译器可能会假设…”来表示规则;扩展该模式并结合编译器可以做出的假设,以及它们在每个示例中的失败情况,将是非常好的。

最佳答案

下面,我将参考sun文件中与该问题相关联的用例。
比较明显的例子是mem_copy()案例,它属于sun论文中的第二个usecase类别(函数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
在第二个函数中,可能有重叠。在这种情况下,我们需要检查源数组是否位于目标数组之前,反之亦然,并相应地选择循环索引边界。
例如,说s1 = 100s2 = 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 = 105s2 = 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;

然后,如果优化器发现这些语句有用,它可以对它们重新排序。
哦,如果指针没有restrict-ed,那么在第二个赋值之前执行第一个赋值是很重要的。这是因为有可能a1[5]a2[3]实际上是同一个内存位置!很容易看出,在这种情况下,最终值应该是8。如果我们重新排列指令,那么结束值将是4!
同样,如果将非不相交指针赋给这个假定代码,则结果是未定义的。

09-10 05:29
查看更多