通过示例了解限制限定符

R..*_*R.. 17 c c99 strict-aliasing restrict-qualifier

restrict关键字的行为在C99定义由6.7.3.1:

设D是普通标识符的声明,它提供了一种将对象P指定为类型T的限制限定指针的方法.

如果D出现在块内并且没有存储类extern,则让B表示该块.如果D出现在函数定义的参数声明列表中,则让B表示关联的块.否则,让B表示主块(或在独立环境中在程序启动时调用的任何函数块).

在下文中,指针表达式E被称为基于对象P if(在评估E之前执行B中的某个序列点)修改P以指向其先前指向的数组对象的副本将改变E.119的值)注意''based''仅为具有指针类型的表达式定义.

在每次执行B期间,让L为具有基于P的&L的任何左值.如果L用于访问它指定的对象X的值,并且X也被修改(通过任何方式),则以下要求适用:T不应该是const限定的.用于访问X值的每个其他左值也应具有基于P的地址.出于本子条款的目的,每次修改X的访问也应被视为修改P. 如果为P分配了指针表达式E的值,该指针表达式E基于与块B2相关联的另一个受限指针对象P2,则B2的执行应在执行B之前开始,或者B2的执行应在该执行之前结束.分配.如果不满足这些要求,则行为未定义.

就像其他人一样,我很难理解这个定义的所有复杂性.作为这个问题的答案,我希望看到第4段中每个要求违反要求的一些好例子.本文:

http://web.archive.org/web/20120225055041/http://developers.sun.com/solaris/articles/cc_restrict.html

在"编译器可能假设......"方面做得很好.扩展该模式并将编译器可以做出的假设以及它们如何无法保持,每个示例都很棒.

ysa*_*sap 8

下面,我将参考与问题相关的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);
Run Code Online (Sandbox Code Playgroud)

因为我们知道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;
}
Run Code Online (Sandbox Code Playgroud)

s2 = '....................1234567890abcde' <- s2 before the naive copy
s1 = '1234567890abcde....................' <- s1 after the naive copy
s2 = '....................1234567890abcde' <- s2 after the naive copy

OTOH,在第二个功能中,可能存在重叠.在这种情况下,我们需要检查源数组是否位于目标之前,反之亦然,并相应地选择循环索引边界.

例如,说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 = 105然后s2 = 100,然后首先写入较低的字节将超过源的最后10个字节s2,并且我们最终得到错误的副本.

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;
}
Run Code Online (Sandbox Code Playgroud)

很容易看出restrict修饰符如何为更好的速度优化提供机会,消除了额外的代码,以及if-else决策.

同时,这种情况对于不合规的程序员来说是危险的,程序员将重叠数组传递给restrict-ed函数.在这种情况下,没有防护装置可以确保正确复制阵列.根据编译器选择的优化路径,结果是未定义的.


第一个用例(该init()功能)可以看作是上述第二个用例的变体.这里,使用单个动态内存分配调用创建两个数组.

将两个指针指定为restrict-ed使得能够进行优化,否则指令顺序将是重要的.例如,如果我们有代码:

a1[5] = 4;
a2[3] = 8;
Run Code Online (Sandbox Code Playgroud)

然后,如果发现它有用,优化器可以重新排序这些语句.

OTOH,如果指针不是 restrict -ed,那么第一个赋值将在第二个赋值之前执行是很重要的.这是因为有可能a1[5]并且a2[3]实际上是相同的内存位置!很容易看出,在这种情况下,那么结束值应该是8.如果我们重新排序指令,那么结束值将是4!

同样,如果给这个restrict假定的代码赋予非不相交的指针,则结果是未定义的.