【问题标题】:Understanding restrict qualifier by examples通过示例了解限制限定符
【发布时间】:2012-08-28 15:40:06
【问题描述】:

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

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

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

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

在每次执行 B 期间,令 L 为任何具有 &L 的左值 P. 如果 L 用于访问它的对象 X 的值 指定,并且 X 也被修改(以任何方式),则以下 适用要求: T 不应是 const 限定的。每隔一个左值 用于访问 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)页面是关于这个主题的非常好的读物。
  • 似乎是 Oratroll 拆除其收购的征兆。用回程机修复它。

标签: c c99 strict-aliasing restrict-qualifier


【解决方案1】:

下面,我将参考问题中链接到的 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' &lt;- s2 before the naive copy
s1 = '1234567890abcde....................' &lt;- s1 after the naive copy
s2 = '....................1234567890abcde' &lt;- s2 after the naive copy

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

例如,说s1 = 100s2 = 105。然后,如果n=15,在复制后新复制的s1 数组将超出源s2 数组的前10 个字节。我们需要确保先复制低字节。

s2 = '.....1234567890abcde' &lt;- s2 before the naive copy
s1 = '1234567890abcde.....' &lt;- s1 after the naive copy
s2 = '.....67890abcdeabcde' &lt;- s2 after the naive copy

但是,如果 s1 = 105s2 = 100,那么首先写入低字节将超出源 s2 的最后 10 个字节,我们最终会得到一个错误的副本。

s2 = '1234567890abcde.....' &lt;- s2 before the naive copy
s1 = '.....123451234512345' &lt;- s1 after the naive copy - not what we wanted
s2 = '123451234512345.....' &lt;- 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 假定代码提供了非不相交的指针,则结果是未定义的。

【讨论】:

  • “作为对这个问题的回答,我希望看到一组很好的示例,针对第 4 段中的每个要求,以及违反要求的用法。”我认为这个答案甚至没有提供一个这样的例子。
  • @Nemo - 真的吗?我想我举了 2 个例子,指的是两个用例,其中为假定限制限定指针的代码提供不相交的数组可能会产生错误的结果。
  • 我不排除我不理解这个问题的可能性。 ;-)
  • 我不反对这个答案,因为它提供了丰富的信息,但我认为你所做的更多地解释了限制的预期用途/目的。我的问题的目的是深入研究有关使用限制指针的规则的真正含义以及尝试使用限制的代码可能(理想情况下是微妙的)方式最终会破坏它们。
  • 因此,如果不清楚(也许我应该编辑答案),这两种情况下重叠数组的示例显示了如何将这些指针与restrict 代码一起使用会违反规则。但又一次 - 很晚 - 也许我只是没有正确回答问题。
猜你喜欢
  • 2013-02-05
  • 1970-01-01
  • 1970-01-01
  • 2011-01-06
  • 2017-03-10
  • 2020-07-29
  • 2020-09-25
  • 2018-07-10
  • 1970-01-01
相关资源
最近更新 更多