Suppose I have a function which takes some pointer parameters – some non-const, through which it may write, and some const through which it only reads. Example:
void f(int * a, int const *b);
Suppose also that the function does not otherwise write to memory (i.e. doesn’t use global variables, fixed addresses, recasting the const pointers as non-const and such tricks).
Now, is it sufficient (as per the C language standard), for achieving the benefits of restrict
for all reads within f()
, to only restrict
the output parameters? i.e. in the example, restrict a
but not b
?
A simple test (GodBolt) suggests such restriction should be sufficient. This source:
int f(int * restrict a, int const * b) {
a[0] += b[0];
return a[0] + b[0];
}
int all_restricted(int * restrict a, int const * restrict b) {
a[0] += b[0];
return a[0] + b[0];
}
int unrestricted(int * a, int const * b) {
a[0] += b[0];
return a[0] + b[0];
}
Produces the same object code for x86_64:
f:
mov eax, DWORD PTR [rsi]
mov edx, DWORD PTR [rdi]
add edx, eax
mov DWORD PTR [rdi], edx
add eax, edx
ret
all_restricted:
mov eax, DWORD PTR [rsi]
mov edx, DWORD PTR [rdi]
add edx, eax
mov DWORD PTR [rdi], edx
add eax, edx
ret
unrestricted:
mov eax, DWORD PTR [rsi]
add eax, DWORD PTR [rdi]
mov DWORD PTR [rdi], eax
add eax, DWORD PTR [rsi]
ret
but that’s not a general guarantee.
7
No, it is not enough.
Suppose also that the function does not otherwise write to memory (i.e. doesn’t use global variables, fixed addresses, recasting the const pointers as non-const and such tricks).
This supposition is insufficient. It would also be necessary that the compiler see the function does not otherwise write to memory that the pointer points to (including any memory that could be accessed via the pointer, such as b[18]
). For example, if the function f
calls bar(b);
, and the compiler cannot see bar
, then it cannot know whether memory that b
points to is modified during execution of f
, even if it is not.
Give this additional premise, that the compiler can see there are no modifications to any memory pointed to via b
, then it does not matter for optimization whether b
is declared with const
and/or restrict
: The compiler knows everything about the memory, and telling it anything more does not add information.
However, it is often not the case that code satisfies this premise. (And even when it does, it may be a nuisance for a programmer to be sure of it.) So let’s consider a case when we do not have the additional premise:
void f(int * restrict a, int const *b)
{
printf("%dn", *b);
bar();
printf("%dn", *b);
}
When bar
is called, the compiler does not know whether *b
is modified. Even though this function does not pass b
to bar
, bar
might access some external object that is *b
or has a pointer to where *b
is, and so bar
can change the object that is *b
. Therefore, the compiler must reload *b
from memory for the second printf
.
If instead we declare the function void f(int * restrict a, int const * restrict b)
, then restrict
asserts that, if *b
is modified during execution of f
(including indirectly, inside bar
), then every access to it will be via b
(directly, as in *b
, or indirectly, as through a pointer visibly copied or calculated from b
). Since the compiler can see bar
does not receive b
, it knows bar
does not contain any accesses to *b
that are based on b
, and therefore it may assume bar
does not change *b
.
Therefore, adding restrict
to a parameter that is a pointer to a const
-qualified type may enable some optimizations, even if all other parameters are also declared restrict
.
1
Suppose I have a function which takes some pointer parameters – some
non-const, through which it may write, and some const through which it
only reads. Example:<code>void f(int * a, int const *b);</code><code>void f(int * a, int const *b); </code>void f(int * a, int const *b);
Suppose also that the function does not otherwise write to memory
(i.e. doesn’t use global variables, fixed addresses, recasting the
const pointers as non-const and such tricks).Now, is it sufficient (as per the C language standard), for achieving
the benefits ofrestrict
for all reads withinf()
, to onlyrestrict
the output parameters? i.e. in the example, restricta
but notb
?
restrict
is one-sided. It licenses the compiler to make assumptions about object accesses via lvalues “based on” the restrict
-qualified pointer that do not depend on whether any other pointer is also restrict
-qualified.
In particular, if you restrict
parameter a
then regardless of whether you also restrict
parameter b
, you license the compiler to assume that during any given execution of f()
,
- if
L
is any lvalue whose address is “based on”a
, and L
is used to access the object it designates (read or write), and- that object is modified by any means during the function’s execution, then
- every access to that object (read or write) during the execution of that function will be performed via an lvalue whose address is “based on”
a
, though not necessarily viaL
in particular. - (For example, that object will not be accessed via an lvalue “based on”
b
but not ona
.)
That is laid out in more detail in C17 section 6.7.3.1, though that text is both complex and a bit fraught.
Thus, in the case you describe, restrict
ing only a
entitles the compiler to assume that reads via pointers derived from b
will never observe writes via pointers derived from a
. Whether it actually will generate different code is an entirely different question, of course.
2
The const
keyword as shown will cause a warning or error if you attempt to modify the value pointed by the pointer, but it will not necessarily prevent you from modifying that value, and it certainly will not prevent someone else from modifying that value. Thus, the compiler may have to assume that such modifications may take place.
For example, if you invoke some function defined in a different source file, the compiler will have no knowledge of what that function does, so it will have to assume that the function may somehow have access to the value pointed by that pointer and it may modify it.
Furthermore, even as you modify the value pointed by the non-const pointer, that non-const pointer may be pointing to the same value as the const pointer. (As in, f( &x, &x );
) Without restrict
on the const pointer, the compiler has to assume that this is also a possibility.
Therefore, even the const
parameter could benefit from the restrict
keyword, because it promises that the memory pointed by that pointer will not be modified by anyone. Essentially, you would be promising that you are never going to do something like f( &x, &x );
.
9