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段中每个要求违反要求的一些好例子.本文:
在"编译器可能假设......"方面做得很好.扩展该模式并将编译器可以做出的假设以及它们如何无法保持,每个示例都很棒.
下面,我将参考与问题相关的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 = 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
,然后首先写入较低的字节将超过源的最后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
假定的代码赋予非不相交的指针,则结果是未定义的.
归档时间: |
|
查看次数: |
2539 次 |
最近记录: |