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