A restrict-qualified pointer parameter of a function is an effective way to allow compilers to optimize that function:
int f(const int *restrict p) {
int n=*p;
printf("Debug\n");
return *p==n;
}
Here Clang 17 with -O3 will just return 1 without re-examining *p
, and without performing the comparison.
Without restrict
, *p
has to be re-examined and the comparison has to be performed. This is because printf
is an external function, and the compiler doesn't know if it might modify *p
. Indeed, if p
happens to be pointing into the file position indicator of stdout
, printf
is actually going to modify it.
So, as a general principle, if you want maximum optimization for the function, and your function ever calls an external function, it is not meaningless to declare all your parameters restrict
. Of course, this places a severe restriction on callers, because it requires them to promise that they will not call your function with pointers aliased into unexpected places.
My question goes further than this observation. I ask if restrict
permits optimization of the callers of your function too?
int f(const int *restrict p);
int caller(int *p) {
int n=*p;
f(p);
return *p==n;
}
It would seem to me that this guarantees to the compiler that f
will not modify *p
, thus it can omit re-examining *p
and the comparison, and can safely return 1. Neither Clang nor GCC do this optimization.
I know it's OK that they don't optimize the caller. I am asking whether the are permitted to optimize. (This is primarily a question about the semantics of my program, not about performance.)
In this code:
int f(const int *restrict p);
int caller(int *p) {
int n=*p;
f(p);
return *p==n;
}
the compiler cannot conclude that *p
does not change during execution of f
because the code below shows a definition of f
and a call to caller
in which the behavior is defined (does not violate the restrict
requirements) yet the value of *p
changes during execution of f
:
extern int m[2];
int f(const int * restrict p)
{
m[0] = 3;
return p[1];
}
void bar(void)
{
caller(m);
}
When f
is called in this case, p
points to m[0]
, so *p
is m[0]
. Since f
changes m[0]
, the value of *p
changes during execution of f
, but this does not violate the restrict
requirements because the restrict
definition only imposes requirements if p
is used, directly or indirectly, to access the object. C 2018 6.7.3.1 4 says:
During each execution of
B
, letL
be any lvalue that has&L
based onP
. IfL
is used to access the value of the objectX
that it designates, andX
is also modified (by any means), then the following requirements apply:…
In the above f
, m[0]
is accessed only through the lvalue m[0]
and not through any lvalue whose address is based on p
. So the prerequisite condition for restrict
never occurs for m[0]
, so it imposes no constraints on whether or how m[0]
is modified.