6

假设我有一个大数组,我计算一个索引并传递给第二个函数。举个简单的例子,比如:

void foo(float* array, float c, unsigned int n)
{
    for (unsigned int i = 0; i < n; ++i)
        array[i] *= c;
}

void bar(float* restrict array, float* restrict array2, unsigned int m, unsigned int n)
{
    for (unsigned int i = 0; i < m; ++i)
        foo(&array[i * n], array2[i], n);
}

这是否违反了 bar() 中的限制规则,您将数组的一部分的地址传递给 foo(),即使您从未真正在 bar() 中为数组的一部分使用别名?

4

2 回答 2

9

(所有引文均参考N1256,即 C99 加技术勘误 (TC3)。)

的正式定义restrict在 §6.7.3.1 中给出。我引用下面最重要的子条款。P是一个restrict限定类型的指针,T其范围是一个块B。一个指针表达式E被说成是基于 P它是否依赖于它P自己的值,而不是它所P指向的值。

在 的每次执行期间B,设L&L基于 P 的任何左值。如果L用于访问X它指定的对象的值,并且X也被修改(通过任何方式),则适用以下要求:

  • T不应是 const 限定的。
  • 用于访问 的值的每个其他左值X也应具有基于 的地址P
  • 就本条而言,修改的每个访问也X应被视为修改P
  • 如果分配了基于另一个受限指针对象P的指针表达式的值,与 block 关联,则 的执行应在 的执行之前开始,或者的执行应在分配之前结束。EP2B2B2BB2

如果不满足这些要求,则行为未定义。


让我们看看规则对bar's arrayin部分的访问有什么规定foo。我们从array的参数列表中声明的限制限定指针开始bar。为清楚起见,我将对以下参数进行 alpha 转换foo

void foo(float* b, float c, unsigned int n) { /*modify b[i]*/ }

指向的存储array也是通过 修改的b。第二个要点没关系,&array[i*n]等同于array+(i*n)(参见第 6.5.3.2 节)。

如果b是restrict-qualified,那么我们必须用P​← bB​← fooP2​← arrayB2​←检查第四个要点bar。由于B嵌套在内部B2(函数的行为就像在这里内联,请参阅 §6.7.3.1.11),因此满足第一个条件。第三个要点(对b[i]in的访问foo)也有一个实例,这不是问题。

但是b没有限制条件。根据 §6.3.2.3.2,“对于任何限定符q,指向非q限定类型的指针可以转换为指向该类型的q限定版本的指针;存储在原始指针和转换指针中的值应比较相等”。因此从array+(i*n)to的转换b是明确定义的并且具有明显的含义,因此程序的行为是定义的。此外,由于b不是restrict-qualified,它不需要遵守任何线性条件。例如,以下foo与 结合是合法的bar

void qux(float *v, float *w) {
    v[0] += w[0];
}
void foo(float* b, float c, unsigned int n)
{
    qux(b,b);
}

ADDED: To address your specific concern “in bar() where you pass the address of part of the array to foo()”, this is a non-issue: restrict applies to the pointer, not the array, and you can perform arithmetic on it (bullet point 2).

于 2010-10-04T19:48:14.510 回答
-2

不,restrict 意味着数组不能为任何东西加上别名,所以你可以在不违反规则的情况下将东西传递给 bar

于 2010-10-04T16:33:04.790 回答