restrict 的 C11 正式定义是否与实现一致?

Is the C11 formal definition of restrict consistent with implementation?

在尝试回答最近的一个问题()时,我找不到 C11 标准如何与实践保持一致。

我并不是要指出标准或其他任何东西,大多数看起来不一致的东西我只是理解不正确,但我的问题最好作为反对标准中使用的定义的论据提出,所以在这里是。

似乎普遍认为一个函数可以接受一个限制限定的指针,并且既可以在它上面工作,又可以有自己的函数调用在它上面工作。例如,

// set C to componentwise sum and D to componentwise difference

void dif_sum(float* restrict C, float* restrict D, size_t n)
{
     size_t i = 0;
     while(i<n) C[i] = C[i] - D[i],
                D[i] += C[i] + D[i],
                ++i;
}



// set A to componentwise sum of squares of A and B
// set B to componentwise product of A and B

void prod_squdif(float* restrict A, float* restrict B, size_t n)
{
     size_t i = 0;
     float x;
     dif_sum(A,B,n);
     while(i<n) x = ( (A[i]*=A[i]) - B[i]*B[i] )/2,
                A[i] -= x,
                B[i++] = x/2;
}

似乎普遍的理解是,限制指针需要在其声明块中引用独立的 space。所以,prod_sqdif 是有效的,因为除了那些指针之外,在它的定义块中没有词法访问由 A 或 B 标识的数组。

为了表达我对标准的关注,这里是 restrict 的标准正式定义(根据委员会草案,如果您有最终版本并且有所不同,请告诉我!):

6.7.3.1 Formal definition of restrict

1 Let D be a declaration of an ordinary identifier that provides a means of designating an object P as a restrict-qualified pointer to type T.

2 If D appears inside a block and does not have storage class extern, let B denote the block. If D appears in the list of parameter declarations of a function definition, let B denote the associated block. Otherwise, let B denote the block of main (or the block of whatever function is called at program startup in a freestanding environment).

3 In what follows, a pointer expression E is said to be based on object P if (at some sequence point in the execution of B prior to the evaluation of E) modifying P to point to a copy of the array object into which it formerly pointed would change the value of E. Note that ‘‘based’’ is defined only for expressions with pointer types.

4 During each execution of B, let L be any lvalue that has &L based on P. If L is used to access the value of the object X that it designates, and X is also modified (by any means), then the following requirements apply: T shall not be const-qualified. Every other lvalue used to access the value of X shall also have its address based on P. Every access that modifies X shall be considered also to modify P, for the purposes of this subclause. If P is assigned the value of a pointer expression E that is based on another restricted pointer object P2, associated with block B2, then either the execution of B2 shall begin before the execution of B, or the execution of B2 shall end prior to the assignment. If these requirements are not met, then the behavior is undefined.

5 Here an execution of B means that portion of the execution of the program that would correspond to the lifetime of an object with scalar type and automatic storage duration associated with B.

6 A translator is free to ignore any or all aliasing implications of uses of restrict.

[Examples not included because they are not formally significant.]

6.2.4 第 6 项的以下摘录可能会认为 B 的执行与其中包含的词法表达式有关:

"...Entering an enclosed block or calling a function suspends, but does not end, execution of the current block..."

然而,restrict 的正式定义的第 5 部分明确定义了块 B 以对应于 B 中声明的具有自动存储的对象的生命周期(在我的示例中,B 是 prod_squdif 的主体) .这显然覆盖了标准中其他地方对块执行的任何定义。以下摘自标准的摘录定义了对象的生命周期。

6.2.4 Storage durations of objects, item 2

The lifetime of an object is the portion of program execution during which storage is guaranteed to be reserved for it. An object exists, has a constant address, and retains its last-stored value throughout its lifetime. 34) If an object is referred to outside of its lifetime, the behavior is undefined. The value of a pointer becomes indeterminate when the object it points to (or just past) reaches the end of its lifetime.

那么dif_sum的执行明明是包含在B的执行里面的,我觉得这里面没有什么问题。但是 dif_sum 中读取和修改 A 和 B 的元素(通过 C 和 D)的左值显然不是基于 A 和 B(它们遵循可以将 A 和 B 重新指向它们的副本的序列点内容而不改变由左值标识的位置)。这是未定义的行为。请注意,第 4 项中讨论的左值或序列点不受限制;如前所述,没有理由将左值和序列点限制为与块 B 在词法上对应的​​那些,因此函数调用中的左值和序列点就像它们在调用函数的主体中一样。

另一方面,正式定义明确允许为 C 和 D 分配 A 和 B 的值这一事实似乎暗示了普遍接受的 restrict 用法。这表明对 A 和 B 的一些有意义的访问通过 C 和 D 是允许的。然而,如上所述,对于通过外部或内部函数调用修改的任何元素,并且至少由内部调用读取,这种访问是未定义的。这似乎与最初允许分配的明显意图背道而驰。

当然,意图在标准中没有正式位置,但它似乎确实暗示了 restrict 的通用解释,而不是实际定义的内容,才是意图。

综上所述,将B的执行解释为B自动存储生命周期内每条语句的执行,那么函数调用就无法使用传递给它们的restrict指针的内容。

对我来说似乎不可避免的是,应该有一些例外说明不考虑函数或子块内的读写,但最多可以在这样的子块(和其他子块,递归地)中进行一次赋值基于外部块中的任何特定限制指针。

我今天和昨天真的超标了。我真的看不出 restrict 的正式定义如何可能与它似乎被理解和实施的方式一致。

编辑:正如已经指出的那样,违反限制合同会导致未定义的行为。我的问题不是关于违反合同时会发生什么。我的问题可以重述如下:

restrict的形式化定义如何与通过函数调用访问数组元素保持一致?在调用函数内的这种访问是否不构成不基于传递给函数的限制指针的访问?

我正在寻找基于标准的答案,因为我同意限制指针应该能够通过函数调用传递。只是这似乎不是标准中正式定义的结果。

编辑

我认为传达我的问题的主要问题与 "based on" 的定义有关。我会尝试在这里以不同的方式提出我的问题。

以下是对 prod_squdif 的特定调用的非正式跟踪。这不是 C 代码,它只是对函数块执行的非正式描述。

请注意,根据 restrict 的正式定义的第 5 项,此执行包括被调用函数的执行:"Here an execution of B means that portion of the execution of the program that would correspond to the lifetime of an object with scalar type and automatic storage duration associated with B."

// 1. prod_squdif is called
prod_squdif( (float[1]){2}, (float[1]){1}, 1 )

// 2. dif_sum is called
dif_sum(A,B,n)   // assigns C=A and D=B

// 3. while condition is evaluated
0<1   // true

// 4. 1st assignment expression
C[0] = C[0] - D[0]   // C[0] == 0

// 5. 2nd assignment expression
D[0] += C[0] + D[0]   // D[0] == 1

// 6. increment
++i // i == 1

// 7. test
1<1   // false

// return to calling function

// 8. test
0<1   // true

// 9. 1st assignment expression
x = ( (A[0]*=A[0]) - B[1]*B[1] )/2   // x == -.5

// 10. 2nd assignment expression
A[0] -= -.5   // A[0] == .5

// 11. 3rd assignment expression
B[i++/*evaluates to 0*/] = -.5/2   // B[0] == -.25

// 12. test
1<1   // false

// prod_squdif returns

因此,限制合同的测试由限制的正式定义中的第 4 项给出:"During each execution of B, let L be any lvalue that has &L based on P. If L is used to access the value of the object X that it designates, and X is also modified (by any means), then the following requirements apply: ... Every other lvalue used to access the value of X shall also have its address based on P..."

设 L 为上面标记为“4”的执行部分 (C[0]) 左侧的左值。 &L 是基于 A 吗?即,C 是否基于 A?

参见 restrict 的正式定义的第 3 项:“...指针表达式 E 被认为是基于对象 P 如果(在 E 求值之前执行 B 的某个序列点)修改P 指向它之前指向的数组对象的副本会更改 E... 的值。

以上述第3项结尾为序点。 (在此序列点)将 A 修改为指向它之前指向的数组对象的副本不会更改 C 的值。

因此 C 不基于 A。因此 A[0] 由不基于 A 的左值修改。由于它也由基于 A 的左值(第 10 项)读取,因此这是未定义的行为.

我的问题是:因此得出我的示例调用未定义行为的结论是否正确,因此 restrict 的正式定义与常见实现不一致?

我真的不确定你的问题是什么。

听起来你在问:

Q: Gee, will "restrict" still apply if I violate the "restrict" contract? Like in the "remove_zeroes()" example?

答案当然是"No - it won't"。

这里有两个链接可能会澄清讨论。请用 (a) 个更明确的问题更新您的 post:

  • Realistic usage of the C99 'restrict' keyword?

  • Is it legal to assign a restricted pointer to another pointer, and use the second pointer to modify the value?

  • https://en.wikipedia.org/wiki/Restrict

假设我们有一个像这样嵌套块的函数:

void foo()
{
  T *restrict A = getTptr();
  {
    T *restrict B = A;
    {
      #if hypothetical
      A = copyT(A);
      #endif
      useTptr(B + 1);
    }
  }
}

似乎在调用 useTptr(B + 1) 时,对 A 的假设更改将不再影响 B + 1 的值。但是,可以找到不同的序列点,这样 A 的变化 会影响 B + 1 的值:

void foo()
{
  T *restrict A = getTptr();
  #if hypothetical
  A = copyT(A);
  #endif    
  {
    T *restrict B = A;
    {
      useTptr(B + 1);
    }
  }
}

和C11草案标准n1570 6.7.3.1 restrict的正式定义只要求有some这样的序列点,而不是所有个序列点都表现出这种行为。