According to the section 6.7.3 of the C99 Draft N1256:
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.117) 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).
Declaring a pointer as restrict ensures the compiler that no other pointer will modify the memory location pointed by the restricted pointer.
However, You can still have two restricted pointers pointing to the same memory region.
Again, the EXAMPLE 3 in 6.7.3.1 of the C99 Draft N1256 comes in handy:
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];
}
This is the comment from the standard:
[The function parameter declarations of h] 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, the answer to your question is: No, this is not a missed optimization by GCC or Clang.
As pointed out by Peter in the comments there could be a missing optimization based on the following undefined behavior related to restricted pointers:
An object which has been modified is accessed through a restrict-qualified pointer to
a const-qualified type, or through a restrict-qualified pointer and another pointer that
are not both based on the same object (6.7.3.1).
Essentially, if two restricted pointers are not used to modify the data they are pointing to (which is the case of your function foo and the function h in my answer), no undefined behavior would occur even if they are pointing to the same memory region. Therefore, the compiler cannot say anything about their values at compilation time: they could be equal as well as different.
However, a different situation is the following:
int compare_pointers(int *restrict ptr1, int *restrict ptr2)
{
*ptr1 = 0;
*ptr2 = 1;
if (ptr1 != ptr2) {
return 1234;
} else {
return 4321;
}
}
Since both pointers are used to modify data they must be associated to different memory region otherwise an undefined behavior would occur (this is a consequence of the restrict
keyword) and so an optimization could remove the subsequent comparison and return 1234 instead. However, both GCC and Clang don't perform such optimization as shown by Peter's comment.
implying
doesn't enforce anything, does it? – Unman*ptr1 = *ptr2 =
access them for restrict to "take effect". – Tobitrestrict
ensures the compiler that no other pointer will be used to access the same location of the restricted pointer; it doesn't ensure you that no other pointer will exist pointing to the same memory location. You need to access the location to have optimizations see godbolt.org/z/eb1j7rr9P and godbolt.org/z/f9G5nW6dT – Philosophicalrestrict
debuted in C99. I don't think I would call that "relatively new" 24 years and three editions of the language spec later. – Meldrestrict
implies the the memory pointed to by a pointer is not aliased by any other pointer" -- no, it doesn't. It sets up conditions in which program behavior is undefined, but where it would be defined withoutrestrict
. Those conditions require one pointer to alias arestrict
-qualified pointer, so when all the other conditions are also present, the compiler is free to assume no aliasing of therestrict
qualified pointer. But that's not the same as "they can't alias", and those conditions are not (all) present in the example code for any input. – Meldrestrict
are predicated on accessing objects, not merely testing their addresses. And, for a hypothetical example where this is useful, consider code that accepts restrict-qualified pointers to a source buffer and a destination buffer. It could compare pointers, and, if they are different, use an algorithm that directly writes results to the destination buffer, whereas, if they are the same (or overlap), uses a different algorithm that, perhaps, reads and writes only through the destination pointer. – Petersburg