Restricted pointer questions - c

I'm a little confused about the rules regarding restricted pointers. Maybe someone out there can help me out.
Is it legal to define nested restricted pointers as follows:
int* restrict a;
int* restrict b;
a = malloc(sizeof(int));
// b = a; <-- assignment here is illegal, needs to happen in child block
// *b = rand();
while(1)
{
b = a; // Is this legal? Assuming 'b' is not modified outside the while() block
*b = rand();
}
Is it legal to derive a restricted pointer value as follows:
int* restrict c;
int* restrict d;
c = malloc(sizeof(int*)*101);
d = c;
for(int i = 0; i < 100; i++)
{
*d = i;
d++;
}
c = d; // c is now set to the 101 element, is this legal assuming d isn't accessed?
*c = rand();
Thanks!
Andrew

For reference, here's the restrict qualifier's rather convoluted definition (from C99 6.7.3.1 "Formal definition of restrict"):
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.
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).
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.
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.
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.
My reading of the above means that in your first question, a cannot be assigned to b, even inside a "child" block - the result is undefined. Such an assignment could be made if b were declared in that 'sub-block', but since b is declared at the same scope as a, the assignment cannot be made.
For question 2, the assignments between c and d also result in undefined behavior (in both cases).
The relevant bit from the standard (for both questions) is:
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.
Since the restricted pointers are associated with the same block, it's not possible for block B2 to begin before the execution of B, or for B2 to end prior to the assignment (since B and B2 are the same block).
The standard gives an example that makes this pretty clear (I think - the clarity of the restrict definition's 4 short paragraphs is on par with C++'s name resolution rules):
EXAMPLE 4:
The rule limiting assignments between
restricted pointers does not
distinguish between a function call
and an equivalent nested block.
With one exception, only
"outer-to-inner" assignments between
restricted pointers declared in nested
blocks have defined behavior.
{
int * restrict p1;
int * restrict q1;
p1 = q1; // undefined behavior
{
int * restrict p2 = p1; // valid
int * restrict q2 = q1; // valid
p1 = q2; // undefined behavior
p2 = q2; // undefined behavior
}
}

The restrict type qualifier is an indication to the compiler that, if the memory addressed by the restrict-qualified pointer is modified, no other pointer will access that same memory. The compiler may choose to optimize code involving restrict-qualified pointers in a way that might otherwise result in incorrect behavior. It is the responsibility of the programmer to ensure that restrict-qualified pointers are used as they were intended to be used. Otherwise, undefined behavior may result. (link)
As you can see from the above description, both your assignments are illegal, that may work in executables produced by some compilers but break in others. Don't expect the compiler itself to emit errors or warnings as restrict just gives an opportunity to perform certain optimization, which it can choose not to perform, like in the case of volatile.

Related

Assigning a non-restricted pointer to a restricted pointer

I am recently implementing a function (my_copy()) with restrict pointers as arguments:
#include <stdio.h>
#include <stdlib.h>
void my_copy(int n, int * restrict p, int * restrict q) {
if (q == NULL) {
q = calloc(n, sizeof(int));
}
while(n-- > 0) {
*p++ = *q++;
}
// Ignore memory leak for now
}
int main() {
int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int b[10];
// Copy a to b
my_copy(10, b, a);
for (int i = 0; i < 10; i++)
printf("%d ", b[i]);
printf("\n");
// Zero a
my_copy(10, a, NULL);
for (int i = 0; i < 10; i++)
printf("%d ", a[i]);
printf("\n");
}
To implement a "default value" in my_copy(), I am assigning to the restricted pointer q. However, I see in https://en.cppreference.com/w/c/language/restrict that using restrict incorrectly can lead to undefined behaviors. Especially, I am confused by the sentence "Assignment from one restricted pointer to another is undefined behavior". Though I believe calloc() will not return a restricted pointer, is my program free of undefined behaviors?
Yes, this is a conforming program.
Here's the relevant portion of the specification:
6.7.3 Type qualifiers
8 An object that is accessed through a restrict-qualified pointer has a special association with that pointer. This association, defined in 6.7.3.1 below, requires that all accesses to that object use, directly or indirectly, the value of that particular pointer.135) The intended use of the restrict qualifier (like the register storage class) is to promote optimization, and deleting all instances of the qualifier from all preprocessing translation units composing a conforming program does not change its meaning (i.e., observable behavior).
You are not accessing the memory pointed to by the q pointer by any other pointer which is not derived from it.
The formal definition of restrict is in 6.7.3.1. Paragraphs 1-4 are relevant here:
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.
D is int * restrict q; it provides a means of designating q as a restrict-qualified pointer to type int. P is q and T is int.
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).
So B is the block defining the function.
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.
q is used only in *q++, so the only pointer expressions modifying P (q) would change are the q and the q++ inside *q++. (Note the purpose of this issue about modifying P to point a copy is so that we can consider what happens if P is changed but it still points to the same values, because they have been copied. In other words, every computation that only involves values that P points to will proceed unchanged; only things that involve the specific value of P will be affected by this change.)
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.
*q++ is an lvalue whose address, &*q++ = q++, depends on P (q). This lvalue L is used to access various bytes X in either the originally passed memory or the allocated memory. However, none of these bytes X is also modified. (We know they do not overlap with any bytes modified in the routine, those pointed to by p, because p is also declared with restrict.) So the “following requirements” listed above do not apply.
In the example shown, you do not need to declare q with restrict; it serves no purpose. Declaring p with restrict suffices to indicate the bytes modified through it will not also be accessed through q.
That is the thrust of the definition above: If you modify some object X through an lvalue L based on a restricted pointer, you will not also access that X through another pointer not derived from the same restricted pointer. So if you assign to q the value of some non-restricted pointer and either never modify memory through some expression based on q or never access that modified memory through some expression not based on q during the lifetime of q, then you are okay.

What are the semantics of assigning restrict pointer to a non-restrict one?

Is the following hypothetical code correct (are the assumptions in comments hold)? Or does it have UB?
#define N 1 // what if it's 0?
void foo(int *x, int * restrict y) {
*x = 42;
*y = 0;
// compiler can assume *x is still 42.
{
int *a = y + N;
*a = 123;
// compiler can no longer assume *x is 42.
} // does the new scope matter in this case?
}
// compiler can no longer assume *x is 42.
I see no basis for this assertion.
// does the new scope matter in this case?
No. The rules for restrict cover a block B that, for a restrict-qualified pointer declared in function parameters, is the block that defines the function. This block within that block B is part of B.
The definition of restrict is given in C 2018 6.7.3.1. Paragraph 1 says:
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.
int * restrict y is a declaration D of the ordinary identifier “y”, and y designates an object P that is a restrict-qualified pointer to an int (type T).
Paragraph 2 says:
… If D appears in the list of parameter declarations of a function definition, let B denote the associated block…
So B is the block that defines the function.
Paragraph 3 says:
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.
So, after int *a = y + N;, a is based on y because modifying y prior to this initialization of a would change the value of a, even if y were changed to point to a copy of the array.
Paragraph 4 says:
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…
*a is an lvalue L that has &L based on y, because &*a is a, and we know a is based on y.
Then, in *a = 123;, this L is used to access the object it designates, and that object is modified. So the requirements must apply: Every other lvalue used to access the object shall also have its addressed based on y.
So, if *x also accessed the object designated by *a, that would violate the requirement above, because &*x is not based on y. x is passed as a separate parameter, and changing the value of y would not change the value of x, so x and &*x are not based on y.
Since the compiler is entitled to expect the requirement is met, it may assume that *a = 123; does not change *x.
If the value stored to a non-restrict pointer is "based upon" the value of a restrict pointer, then operations using that value are considered to be operations performed upon the original. This would work out quite intuitively in situations where one pointer or lvalue is definitely based upon a restrict-qualified pointer and another definitely isn't. Unfortunately, rather than using simple rules for when a pointer is "definitely based upon P" or "definitely not based upon P" another, and allowing for pointers that don't fall into either category (they might be based on P, but can't be proven to be), the Standard instead uses a more complicated rule with absurd, unworkable, and ambiguous corner cases. This wouldn't be a problem if compilers recognized that a pointer value which is formed from a restrict-qualified pointer in clear and obvious fashion should be regarded as based upon that pointer even in corner cases where the Standard wouldn't require that, but clang and gcc don't treat things that way.
For example, given:
int x[1];
int foo(int *restrict p)
{
*p = 1;
if (p == x)
{
*p = 2;
}
return *p;
}
it would seem intuitively obvious that the pointer used in the assignment *p = 2; is based upon restrict p, but the way the Standard is worded makes that ambiguous. Replacing p with the address of a copy of *p would neither result in the assignment being performed to the same address nor to a different address, but for the pointer used in the assignment to be "based upon p", such a change to p would need to change the address used for the assignment. Both clang and gcc take the view that preventing an assignment from being performed at all doesn't "change" the address used in that assignment, and thus they don't recognize the pointer used in the assignment *p = 2; as having any relationship to restrict p.

Assigning two restrict qualified pointers to the same data in C11?

I have been working a lot with restrict qualified pointers, and I thought of something that, although I wouldn't do it, the fact that it seems to be standard compliant makes me question whether there is something I am missing in my understanding of the standard. In particular, as far as I can tell, one could, in a standard compliant way, get two restrict qualified pointers pointing to the same block of data. Consider the following example.
int* restrict p = (int[2]){0,0};
int* restrict* x = (int* restrict*)(int*[1]){p};
**x = 1;
p[1] = 2;
I assign an int* that is not restrict qualified to p, so that does not violate the portion of the standard relating to assigning restrict qualified pointers to each other, 6.7.3.1 paragraph 4. Here is the whole of 6.7.3.1, for reference.
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. 137)
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.
So, then I cast a pointer to an unqualified type to a pointer to a qualified version of that type, allowed under 6.3.2.3 paragraph 2:
For any qualifier q, a pointer to a non-q-qualified type may be converted to a pointer to
the q-qualified version of the type; the values stored in the original and converted pointers
shall compare equal.
I then store this converted pointer to a new object, x. Paragraph 4 seems to not cover this, because x is not being assigned from an expression based on an object P that is designated through a declaration D of an ordinary identifier (there is no such declaration relating to the object from which x is assigned).
Now *x is an object with a restrict qualified effective type, and it is based on p. Modifying one element of the array with both pointers would violate the condition that only expressions based on *x can modify any object that is ever modified by an expression based on *x, so it is hard to see how you can get things too out of hand like this.
Nonetheless, there is a less contrived situation I am dealing with (that would be too long for a question here), where anything disallowing this might be relevant to what I am doing.
So, in short, my question is:
Is the above code I gave standard compliant in C11?

Assigning a restrict-qualified pointer argument to a local restrict-qualified variable

After reading the C standard, 6.7.3.1 "Formal definition of restrict", i have the following misunderstanding. I wonder if the following code instantly causes undefined behavior:
void foo(int *restrict p) {
int *restrict q = p;
}
It is clear that q is assigned the value that is based on another restricted pointer p. What is unclear is if these two pointers are associated with the same block (the function itself), or with different blocks (p with the function itself, q with its compound statement body), because, eg.
int *restrict p;
{
int *restrict q = p;
}
does not cause undefined behavior (it is OK to create aliasing pointers in sub-blocks).
The top answer in MSVC++ restrict keyword and local variables suggests that int *restrict q = p + 1; would be fine, however [at least, in case of the C standard] it's not true, because expression p + 1 is still based on p.
The definition of the block is in 6.7.3.1 p2:
If [a certain pointer declaration] 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. [...]
So is "the associated block" of the function the same thing as the function body in this context? Because i didn't instantly find any explanation of the term "associated block" earlier in the text.

Can you use restrict-ed pointers to access the same object in some cases?

Most definitions of restrict say that it's a promise from the programmer to the compiler that for the lifetime of the pointer, the pointer is the only way that object is accessed. This allows the compiler to optimize the output, because it knows that it will be accessed only by one pointer and thus can be changed only by it. Which if I understand correctly usually means that the program doesn't have to reload the value the pointer points to.
If this is correct then there should be some exceptions when the restrict keyword should be usable even if it goes against the intent of how it should be used.
One thing that comes to mind would be when the data the pointer points to never actually changes during the lifetime of the pointer. In such case there is no need to reload the data even if the pointers point to the same location, because they don't change in the lifetime of the pointers. E.g.:
int max(int *restrict a, int *restrict b) {
return((*a > *b) ? *a : *b);
}
int main(void) {
int num = 3;
int max = max(&num, &num);
}
Is this a valid use of restrict even though it goes against how it was supposed to be used? Will using the restrict keyword like this result in undefined behaviour?
As Eric says the in a comment that is now gone, the key phrase from the C99 draft standard 6.7.3.1 Formal definition of restrict is:
`If… X is also modified…`
this interpretation is supported by this example in 6.7.3.1/10:
void h(int n, int * restrict p, int * restrict q, int * restrict r)
{
int i;
for (i = 0; i < n; i++)
p[i] = q[i] + r[i];
}
and the following comment with the code sample:
illustrate how an unmodified object can be aliased through two restricted pointers. In particular, if a and b are disjoint arrays, a call of the form h(100, a, b, b) has defined behavior, because array b is not modified within function h.
So it would seem that your specific example is defined behavior since you are not modifying a or b.
You may sometimes use restrict-qualified pointers to access the same objects as other pointers, but only if the pointed-to objects are not modified. Here are C 2011 (N1570) 6.7.3.1 paragraphs 1-3 and the first part of paragraph 4 interspersed with how they apply to the code in the question.
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.
So int * restrict a is such a declaration D. When max is called with max(&num, &num);, the object P is num (or, more formally, the object named by num), and T is int. Similarly, int * restrict b is another such declaration.
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).
These declarations appear in the parameter declarations of a function definition, so B is the block of the function definition, that is, the body of max.
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.137) Note that ‘‘based’’ is defined only for expressions with pointer types.
The function max contains pointer expressions a and b, twice each, so these are each an instance of a pointer expression E. These expressions depend on the parameters a and b, respectively, because if we changed a to point to a copy of num instead of pointing to num, then a would have a different value (obviously), and similarly for b. (Although num is a scalar object, it acts like an array containing a single element for purposes of pointer arithmetic.)
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:…
During the execution of max, the lvalue *a has its address (&*a, which is a) based on P (a), so the lvalue *a is an instance of L. This lvalue is used to access num, so num is an instance of an object X. However num is never modified during the execution of max. Therefore, the requirements that follow do not apply. Similarly the lvalue *b refers to an object (num) that is never modified during the execution of max.
Therefore, the code in max does not violate the requirements for restrict, and its behavior is defined by the C standard.

Resources