理解限制的例子预选赛(Understanding restrict qualifier by exa

2019-06-27 18:56发布

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

让d是提供指定对象P作为限制限定指针类型T的一个装置的一个普通标识符的声明

如果d出现块内并且不具有存储类的extern,令B表示该块。 如果d出现在函数定义的参数声明的列表中,令B表示相关联的块。 否则,令B表示的主块(或任何的功能是在独立环境中称为在程序启动时的块)。

在下文中,一个指针表达式E被认为是基于对象p如果(在B的前E的评估执行一些序列点)修饰p来指向阵列对象的副本到它以前指出会改变E.119的值)另外,“”基于“”只是为了与指针类型的表达式定义。

B的各执行过程中,设L是如果L被用来访问对象X,它指定的值,并且X也被修改(通过任何方式),其具有与基于P. L所有左值,则满足下列要求:T不得const限定。 用于访问X的值每隔左值也应具有基于P.其地址修改X将被也被认为修改P,该条款的目的,每一个访问。 如果P被分配指针表达式E的值,该值是基于另一个限制指针对象P2,与块B2,然后相关联的任一B2的执行应B的执行,或B2的执行之前开始应之前结束分配。 如果这些要求得不到满足,则行为是不确定的。

像只是别人好,我也很难理解这个定义的所有复杂。 作为这个问题的答案,我想看看一组很好的例子,每个需求在第4款,这将违反规定用途的。 本文:

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

确实来讲呈现规则的一个好工作“编译器可以假设......”; 扩大在该模式,并在假设编译器就搭售,以及他们如何不抱,每个例子将是巨大的。

Answer 1:

下面,我将引用usecases问题中已链接到太阳纸业。

(相对)不明显的情况下将是mem_copy()的情况下,其下的太阳纸业(在第2个用例类别属于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 = 100s2 = 105 。 然后,如果n=15 ,则复制之后新复制s1阵列将溢出的前10个字节的源的s2阵列。 我们需要确保我们先复制的低字节。

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

然而,如果s1 = 105s2 = 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;
}

这是很容易看到的restrict修改给出了更好的速度优化的机会,消除了额外的代码,并且如果其他决定。

与此同时,这种情况是有害的不谨慎程序员,谁传球重叠排列的restrict -ed功能。 在这种情况下,没有守卫在那里为确保阵列的复制正确。 根据由编译器选择的优化路径上,其结果是不确定的。


的第一用例(所述init()函数)可以被看作是在第二一个的变化,如上所述。 在这里,两个阵列与单个动态存储器分配呼叫建立。

指定两个指针作为restrict -ed能够优化其中的指令顺序,否则无所谓。 例如,如果我们的代码:

a1[5] = 4;
a2[3] = 8;

那么,如果它发现它有用的优化可以重新排列这些语句。

OTOH,如果指针没有 restrict -ed,那么重要的是,第一次分配将在第二个之前进行。 这是因为有一种可能性,即a1[5]a2[3]实际上是相同的内存位置! 这是很容易看到,如果是这种情况,那么最终值应该是8。如果我们重新排序的说明,那么最终值将是4!

再次,如果非相交指针给予本restrict -ed假定代码,结果是不确定的。



文章来源: Understanding restrict qualifier by examples