C11的正式限制是否与实施一致?

时间:2016-04-01 00:36:08

标签: c pointers language-lawyer c11 restrict

在尝试回答最近的问题(Passing restrict qualified pointers to functions?)时,我无法找到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;
}

似乎常见的理解是,限制指针需要在其声明块中引用独立空间。因此,prod_sqdif是有效的,因为在其定义块中没有任何词法访问除了那些指针之外的A或B标识的数组。

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

  

6.7.3.1限制的正式定义

     

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

     

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

     

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

     

4在每次执行B期间,让L为基于P的具有&amp; L的左值。如果L用于访问它指定的对象X的值,则X也被修改(以任何方式) ,则以下要求适用:T不应符合要求。用于访问X值的每个其他左值也应具有基于P的地址。出于本子条款的目的,每次修改X的访问也应被视为修改P.如果为P分配了指针表达式E的值,该指针表达式E基于与块B2相关联的另一个受限指针对象P2,则B2的执行应在执行B之前开始,或者B2的执行应在该执行之前结束。分配。如果这些   如果要求不符合,则行为未定义。

     

5这里执行B意味着程序执行的一部分将对应于具有标量类型的对象的生命周期和与B关联的自动存储持续时间。

     

6翻译人员可以自由忽略使用限制的任何或所有别名含义。

     

[未包含的例子,因为它们不具有正式意义。]

使用词汇中包含的表达式识别B的执行情况可能会被以下6.2.4第6项摘录所支持:

  

&#34; ...输入一个封闭的块或调用一个函数暂停,但不会结束,   执行当前块...&#34;

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

  

6.2.4对象的存储持续时间,第2项

     

对象的生命周期是程序执行的一部分,在此期间保证为其保留存储。存在一个对象,具有一个常量地址,并在其整个生命周期内保留其最后存储的值。 34)如果一个对象在其生命周期之外被引用,则该行为是不确定的。当指针指向(或刚刚过去)的对象到达其生命周期的末尾时,指针的值变得不确定。

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

另一方面,普遍接受的限制似乎暗示了这样一个事实,即正式定义明确允许C和D被赋值为A和B.这表明通过C对A和B进行了一些有意义的访问。和D是允许的。但是,如上所述,对于通过外部或内部函数调用修改的任何元素,以及至少通过内部调用读取,此类访问是未定义的。这似乎与首先允许转让的明显意图相反。

当然,意图在标准中没有正式的位置,但似乎暗示限制的共同解释,而不是看似实际定义的,是预期的。

总之,在B自动存储的生命周期中,将B的执行解释为每个语句的执行,然后函数调用不能处理传递给它们的限制指针的内容。

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

我今天和昨天都已经超越了标准。我真的无法看到限制的正式定义如何与它似乎被理解和实现的方式一致。

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

限制的正式定义如何通过函数调用与数组元素的访问一致?在调用函数中,这种访问不构成不基于传递给函数的限制指针的访问吗?

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

修改

我认为传达我的问题的主要问题与&#34;基于&#34;的定义有关。我会尝试在这里稍微改变一下我的问题。

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

请注意,此执行包括执行被调用函数,对于restrict的正式定义的第5项:&#34;这里执行B意味着程序的执行部分将对应于生命周期标量类型和与B关联的自动存储持续时间的对象。&#34;

// 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项给出:&#34;在B的每次执行期间,令L为具有基于P的&amp; L的任何左值。如果L是用于访问它指定的对象X的值,并且X也被修改(通过任何方式),然后适用以下要求:...用于访问X值的每个其他左值也应该具有基于的地址p ...&#34;

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

参见restrict的正式定义的第3项:&#34; ...指针表达式E被认为是基于对象P if(在评估E之前执行B的某个序列点)修改P指向它以前指向的数组对象的副本将改变E的值...&#34;。

将序列点作为上面第3项的结尾。 (在这个序列点)修改A指向它以前指向的数组对象的coppy不会改变C的值。

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

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

2 个答案:

答案 0 :(得分:4)

假设我们有一个带嵌套块的函数,如下所示:

  // Draw wires to be connected as output
  wire [63:0] t1,t2,ti1,ti2;

  // Drive all regs from values of wires
  always @*
    begin
      outr1 = t1;
      outr2 = t2;
      outi1 = ti1;
      outi2 = ti2;
    end

  // Change : Wires connection
  add_sub adder1(en,clk,inr1[63],inr2[63],inr1[62:52],inr2[62:52],inr1[51:0],inr2[51:0],1'b0,t1[63],t1[62:52],t1[51:0]);
  //...

似乎在调用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

和C11草案标准n1570 6.7.3.1限制的正式定义只要求某些这样的序列点,而不是所有序列积分表现出这种行为。

答案 1 :(得分:1)

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

听起来你在问:

  问:如果我违反“限制”,那么“限制”仍然适用   合同?就像在“remove_zeroes()”示例中一样?

答案当然是“不 - 它不会”。

以下两个链接可能会澄清讨论。请使用(a)更明确的问题更新您的帖子: