2

In trying to answer a recent question (Passing restrict qualified pointers to functions?), I could not find how the C11 standard is consistent with practice.

I'm not trying to call out the standard or anything, most things that look inconsistent I just am not understanding right, but my question is best posed as an argument against the definition used in the standard, so here it is.

It seems to be commonly accepted that a function can take a restrict qualified pointer and both work on it and have its own function calls work on it. For example,

// 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;
}

What seems to be the common understanding is that restrict pointers need to reference independent space within their declaring block. So, prod_sqdif is valid because nothing lexically within its defining block accesses the arrays identified by A or B other than those pointers.

To demonstrate my concern with the standard, here is the standard formal definition of restrict (according to the committee draft, if you have the final version and it is different, let me know!):

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.]

Identifying execution of B with expressions lexically contained therein might be seen as supported by the following excerpt from 6.2.4, item 6:

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

However, part 5 of the formal definition of restrict explicitly defines the block B to correspond to the lifetime of an object with automatic storage declared in B (in my example, B is the body of prod_squdif). This clearly overrides any definition of the execution of a block found elsewhere in the standard. The following excerpt from the standard defines lifetime of an object.

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.

Then the execution of dif_sum is clearly included in the execution of B. I don't think there is any question there. But then the lvalues in dif_sum that read and modify elements of A and B (via C and D) are clearly not based on A and B (they follow sequence points where A and B could have been repointed to copies of their content without changing the locations identified by the lvalues). This is undefined behavior. Note that what lvalues or sequence points are discussed in item 4 is not restricted; as it is stated, there is no reason to restrict lvalues and sequence points to those lexically corresponding to the block B, and so lvalues and sequence points within a function call play just like they do in the body of the calling function.

On the other hand, the generally accepted use of restrict seems implied by the fact that the formal definition explicitly allows C and D to be assigned the values of A and B. This suggests that some meaningful access to A and B through C and D is allowed. However, as argued above, such access is undefined for any element modified through either the outer or inner function call, and at least read by the inner call. This seems contrary to the apparent intent of allowing the assignment in the first place.

Of course, intent has no formal place in the standard, but it does seem suggestive that the common interpretation of restrict, rather than what seems actually defined, is what is intended.

In summary, interpreting the execution of B as the execution of each statement during the lifetime of B's automatic storage, then function calls can't work with the contents of restrict pointers passed to them.

It seems unavoidable to me that there should be some exception stating reads and writes within functions or sub blocks are not considered, but that at most one assignment within such a sub block (and other sub blocks, recursively) may be based on any particular restrict pointer in the outer block.

I have really gone over the standard, both today and yesterday. I really can't see how the formal definition of restrict could possibly be consistent with the way it seems to be understood and implemented.

EDIT: As has been pointed out, violating the restrict contract results in undefined behavior. My question is not about what happens when the contract is violated. My question can be restated as follows:

How can the formal definition of restrict be consistent with access to array elements through function calls? Does such access, within a calling function, not constitute access not based on the restrict pointer passed to the function?

I am looking for an answer based in the standard, as I agree that restrict pointers should be able to be passed through function calls. It just seems that this is not the consequence of the formal definition in the standard.

EDIT

I think the main problem with communicating my question is related to the definition of "based on". I will try to present my question a little differently here.

The following is an informal tracking of a particular call to prod_squdif. This is not intended as C code, it is just an informal description of the execution of the function's block.

Note that this execution includes the execution of the called function, per item 5 of the formal definition of restrict: "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

So, the test for the restrict contract is given by item 4 in the formal definition of restrict: "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..."

Let L be the lvalue on the left of the portion of the execution marked '4' above (C[0]). Is &L based on A? I.e., is C based on A?

See item 3 of the formal definition of restrict: "...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...".

Take as a sequence point the end of item 3 above. (At this sequence point) modifying A to point to a coppy of the array object into which it formerly pointed would NOT change the value of C.

Thus C is not based on A. So A[0] is modified by an lvalue not based on A. Since it is also read by an lvalue that is based on A (item 10), this is undefined behavior.

My question is: Is it correct to therefore conclude that my example invokes undefined behavior and thus the formal definition of restrict is not consistent with common implementation?

Community
  • 1
  • 1
Kyle
  • 878
  • 6
  • 14
  • 1
    I don't follow. Of course the lifetime of an object with automatic storage duration continues during the execution of a function called in the block where the object is in scope. You couldn't pass its address to a called function otherwise. – EOF Apr 01 '16 at 00:47
  • It is not clear to me how you come to whatever conclusion. However, I have the impression you think the execution of `dif_sum` is part of the block where it is called. If so, your prerequisite is already wrong. – too honest for this site Apr 01 '16 at 00:51
  • @EOF Yes, I am not disputing that. The fact that the lifetime persists in the called function, together with the fact that the execution of B (as defined above) is tied to that lifetime, shows that the restrictions on lvalues persist in the function call as well. – Kyle Apr 01 '16 at 00:51
  • @Olaf, the execution of dif_sum, as far as I can tell, is considered part of the execution of the calling function for these purposes, per item 5 in the formal definition of restrict. That is what I don't get. The execution of dif_sum is a portion of the program execution corresponding to automatic storage duration lifetime of the calling function. – Kyle Apr 01 '16 at 00:53
  • But it is not part of the context of the block, i.e. is does not have access to the local variables. IOW: it does not become _part_ of the block. I have the faint impression you confuse scope (compile-time structure), life-time (not sure how that is related to the problem) and call-chain (run-time issue). Sorry not sure how I can be clearer what I mean. As I'm not sure if it is just me not understanding your question or it is really not clear, I'll leave this discussion to the native speakers. – too honest for this site Apr 01 '16 at 01:02
  • Your question would be improved if you can post a [MCVE](http://stackoverflow.com) showing behaviour that you claim is undefined (but you think should be defined). – M.M Apr 01 '16 at 01:08
  • Sequence points have nothing to do with this – M.M Apr 01 '16 at 01:09
  • @M.M, the property of an expression being based on a pointer is given in item 3 above. In that context, C is not based on A. Take a sequence point in the called function, but before C is used. At that sequence point, copy A to new space, and reassign A to that array. The lvalue expressions based on C do not change as a result. So, as I understand the "based on" definition, this means those lvalues are not based on A. – Kyle Apr 01 '16 at 01:10
  • Use code to show what you mean, instead of describing some possibility – M.M Apr 01 '16 at 01:11
  • @M.M, so that is why I see sequence points as relevant, they are part of the definition of "based on" in item 3 above. – Kyle Apr 01 '16 at 01:11
  • @M.M I would be happy to, but I don't understand exactly what you would like to see. I have not had problems with this issue that I could show you explicitly, I am just worried about my understanding of the standard. What could I add to my two functions to point out my concern? It seems to me that these two function demonstrate behavior that is typically understood to be defined. I have done my best to describe why I believe the standard calls the behavior of `prod_dif` undefined. I'm happy to include anything else you feel would help. – Kyle Apr 01 '16 at 01:15
  • You are talking about "copy A to new space and reassign A" etc. but your code sample does not include reassigning A. Are you claiming your code sample is undefined or what? You should show a MCVE that demonstrates the problem you are talking about. – M.M Apr 01 '16 at 01:15
  • The copy thing is a reference to item 3 in the definition: "modifying P to point to a copy of the array object into which it formerly pointed would change the value of E". It is the hypothetical potential for copy discussed in the definition. – Kyle Apr 01 '16 at 01:16
  • Maybe you could have read to the bottom of the page, where a footnote reveals your misunderstanding? *137) In other words, E depends on the value of P itself rather than on the value of an object referenced indirectly through P. For example, if identifier p has type (int **restrict), then the pointer expressions p and p+1 are based on the restricted pointer object designated by p, but the pointer expressions *p and p[1] are not.* – EOF Apr 01 '16 at 01:17
  • @EOF, The footnotes are not formally part of the standard. Also, it is debatable whether or not this applies to C. C is based on the value of A when it is assigned, but subsequent expressions using C are based on the value of C, not on the value that still is stored in A. This is the distinction drawn by item 3 of the definition. For example, if A were assigned to A_ in the calling function, expressions based on A_ would not be based on A, even though the original assignment was based on A. – Kyle Apr 01 '16 at 01:19
  • @Olaf, please go on if you can. What you are saying sounds like a possible explanation. My only concern is that item 5 of the definition seems to refer to the execution of the program, which would include the content of function calls. Also, sub blocks seem to be implied to behave like function calls in the examples following the formal definition, and sub-blocks seem to be compile time structures. I'd appreciate it if you have more to say on the matter. – Kyle Apr 01 '16 at 01:22
  • I think you mean, if `A_` were assigned to `A`. (i.e. `A = A_;`) But your code doesn't actually do this. Nor is it relevant in any way. You won't provide code sample so I assume you are describing something like `dif_sum(A,B,n); A = A_;` But the behaviour of the code in `dif_sum` was defined by what happens up to that point, not what happens afterwards. `C` is based on what `A` was when you called `dif_sum`, not any subsequent assignment. `C` doesn't even exist by the time `A = A_;` is reached. – M.M Apr 01 '16 at 01:23
  • @M.M, If any particular code samples would help, please give me some direction on what I could provide. I think the issue though is that the formal definition of restrict includes hypothetical pieces of code (in item 3, hypothetically copying the contents of P and pointing P to the copy). This is not meant to refer to anything actually in the code, but rather to what could hypothetically be in the code. I am just trying to apply this hypothetical rule to my situation. I have not included code demonstrating such a copy, because the copy is hypothetical. – Kyle Apr 01 '16 at 01:27
  • writing `A = A_;` *before* the call to `dif_sum` would mean `C` has a different value than it would have if you hadn't written that, therefore `C` is based on `A`. (This is what the point 3 says) – M.M Apr 01 '16 at 01:27
  • @Kyle: I really would like to, but be it a hard day here or just lack of the right phrases, I'm not sure how to express what I mean in English. Maybe someone can catch up my thought. If I find the time, I'll revisit this the next few days. – too honest for this site Apr 01 '16 at 01:28
  • The trouble is that IDK what hypothetical code you are imagining. I'm suggesting that you write some code which would be an instance of this hypothetical code, so we can discuss it. – M.M Apr 01 '16 at 01:30
  • @Olaf, sounds good, thanks. – Kyle Apr 01 '16 at 01:31
  • @M.M, OK, I am going to try to add to my question in a way that demonstrates what I am saying. Please check back in 15 minutes or so. – Kyle Apr 01 '16 at 01:33
  • 1
    @Kyle: Note that in *6.7.3.1p3* it says "at some sequence point", not "at all sequence points". Can you find a sequence point prior to some expression using the value of `C`, for which changing the value of `A` changes the value of `C`? If so, `C` is based on `A`. – EOF Apr 01 '16 at 01:44
  • @EOF, Thank you. I really wish the whole question didn't hinge on one word. Wow. But that resolves the issue for me. If you'd like to have your answer selected, go ahead and copy that into an answer. – Kyle Apr 01 '16 at 02:11
  • @Kyle: Ah, the old "existential quantifier" vs. "universal quantifier"-issue. Not sure how to turn this into an answer that is of any use to anyone else. – EOF Apr 01 '16 at 02:15
  • Even for your chosen sequence point though, you write "So A[0] is modified by an lvalue not based on A. Since it is also read by an lvalue that is based on A (item 10)" , however in item 10 `A` has the value you changed it to in step 3, so item 10 does not read the same object that item `4` modifies. – M.M Apr 01 '16 at 02:32

2 Answers2

4

Suppose we have a function with nested blocks like this:

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

It would seem that, at the point where useTptr(B + 1) is called, the hypothetical change to A would no longer affect the value of B + 1. However, a different sequence point can be found, such that a change to A does affect the value of B + 1:

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

and C11 draft standard n1570 6.7.3.1 Formal definition of restrict only demands that there be some such sequence point, not that all sequence points exhibit this behavior.

EOF
  • 6,273
  • 2
  • 26
  • 50
  • I think a lot of pointer-aliasing issues could have been much more cleanly handled if the Standard were to describe what compilers are allowed to do, and say that a program only has defined behavior if no allowable compiler transformation would result in Undefined Behavior. Say that a compiler is allowed to take a snapshot of anything a pointer might access any time between the creation of the pointer and its first use, copy that snapshot back any time between the pointer's last use and the destruction of the last pointer derived from it, either by overwriting or by departure of scope. – supercat Apr 02 '16 at 15:59
  • @supercat: I don't find your description in the least compelling. As best as I can tell, you'd basically allow writes to pointers to be buffered until the pointer is invalidated. That's insane. Can you imagine the kind of race conditions you'd get? That kind of behavior is permissible in mutlithreaded environments between threads, because you really can't avoid it, but *within* a thread sequential consistency is rather important in order to make programming possible without going insane. – EOF Apr 02 '16 at 16:13
  • My language doesn't quite match the behavior of `restrict`, but it serves the intended purpose and should be quite clear: Given `{restrict *p = &foo; .... }`, operations on objects identified by "p" or pointers derived from it [though not on pointers from which p is derived] will be sequenced after the open brace and before the close brace, but unsequenced relative to anything between them [including pointers from which p is derived]. There are many circumstances where such semantics are just perfect, and I think describing things in such terms is a lot clearer than the Standard. – supercat Apr 02 '16 at 17:19
  • I should have clarified that there should be a distinction between two flavors of restrict--one where the writeback would be sequenced relative to the lifetime of the original pointer (with the "restriction" on copies only lasting as long as the original variable), and one which would imply that no copies of the pointer would be written after the next access of the object by any other means, and no copy of of the pointer would be accessed after the object was written by any other means. – supercat Apr 02 '16 at 17:28
  • On modern pipelined processors, optimization often requires rearranging the order in which independent statements execute, and that in turn requires that the compiler be informed of what statements can be considered "independent". The lack of thread consistency in cases where a programmer specifies that things don't need to be ordered doesn't strike me as alarming compared with the lack of thread consistency when two structure types share a Common Initial Sequence, and a pointer to one is used to access part of the CIS of the other--a behavior some compiler writers seem proud of. – supercat Apr 02 '16 at 17:31
1

I'm really not sure exactly what your question is.

It sounds like you're asking:

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

The answer, of course, is "No - it won't".

Here are two links that might clarify the discussion. Please update your post with (a) more explicit question(s):

Community
  • 1
  • 1
paulsm4
  • 114,292
  • 17
  • 138
  • 190
  • What I am saying is that the formal definition does not distinguish between sequence points and lvalues in a called function or sub-block, and those in the primary block. This then causes accesses in the called function or sub-block to violate the contract. It seems that the formal definition causes unintended violations of the contract, due to the way the contract is defined. – Kyle Apr 01 '16 at 00:57
  • The "remove_zeros()" example is just there to point out that sub blocks can't be treated differently. I'm going to remove that example, I think it obscures my question. – Kyle Apr 01 '16 at 00:57