Suppose we have a function declaration for which we do not have access to its definition:
void f(int * restrict p, int * restrict q, int * restrict r);
Since we do not know how the pointers will be accessed, we cannot know if a call will trigger undefined behavior or not -- even if we are passing the same pointer, like the example at 6.7.3.1.10 explains:
The function parameter declarations:
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];
}
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
.
Therefore, is restrict
superfluous in these cases, except as a hint/annotation for callers, unless we know something more about the function?
For instance, let's take sprintf
(7.21.6.6) from the standard library:
Synopsis
#include <stdio.h>
int sprintf(char * restrict s,
const char * restrict format, ...);
Description
The sprintf
function is equivalent to fprintf
, except that the output is written into an array (specified by the argument s
) rather than to a stream. (...)
From the synopsis and the first sentence of the description, we know that s
will be written to and that s
is a restricted pointer. Therefore, can we already assume (without reading further) that a call like:
char s[4];
sprintf(s, "%s", s);
will trigger undefined behavior?
If yes, then: is the last sentence of sprintf
's description superfluous (even if clarifying)?
If copying takes place between objects that overlap, the behavior is undefined.
If not, then, the other way around: is the restrict
qualifier superfluous since the description is the one that is actually letting us know what will be undefined behavior?
If yes, then: is the last sentence of sprintf's description superfluous (even if clarifying)?
If copying takes place between objects that overlap, the behavior is undefined.
int sprintf(char * restrict s, const char * restrict format, ...);
The restrict
on s
means that reads and writes only depends on what sprintf()
does. The following code does that, reading and writing data pointed by p1
as the char * restrict s
argument. Read/write only happened due to direct sprintf()
code and not a side effect.
char p[100] = "abc";
char *p1 = p;
char *p2 = p;
sprintf(p1, "<%s>", p2);
Yet when sprintf()
accesses the data pointed to by p2
, there is no restrict
. The “If copying takes place between objects that overlap, the behavior is undefined” applies to p2
to say p2
's data must not change due to some side effect.
If not, then, the other way around: is the restrict qualifier superfluous since the description is the one that is actually letting us know what will be undefined behavior?
restrict
here is for the compiler to implement the restrict
access. We do not need to see it given the “If copying takes place...” spec.
Consider the simpler strcpy()
which has the same “If copying takes place between objects that overlap, the behavior is undefined.”. It is redundant for us, the readers, here, as careful understanding of restrict
(new in C99), would not need that.
char *strcpy(char * restrict s1, const char * restrict s2);
C89 (pre-restrict
days) also has this wording for strcpy(), sprintf(), ...
and so may be simply a left-over over-spec in C99 for strcpy()
.
The most challenging aspect of type * restrict p
I find is that it refers to what will not happen to its data (p
data will not change unexpectedly - only through p
). Yet writing to p
data is allowed mess up others - unless they have a restrict
.
Given a function signature like:
void copySomeInts(int * restrict dest, int * restrict src, int n);
someone who wanted the function to yield defined behavior in the case where the source and destination overlap [or even where they are equal] would need to expend some extra effort to do so. It would be possible, e.g.
void copySomeInts(int * restrict dest, int const * restrict src, int n)
{
for (int i=0; i<n; i++)
{
if (dest+i == src)
{
int delta = src-dest;
for (i=0; i<n; i++)
dest[i] = dest[delta+i];
return;
}
if (src+i == dest)
{
int delta = src-dest;
for (i=n-1; i>=0; i--)
dest[i] = src[delta+i];
return;
}
}
/* No overlap--safe to copy in normal fashion */
for (int i=0; i<n; i++)
dest[i] = src[i];
}
and thus there's no way a compiler generating code to call copySomeInts
would be able to make any inferences about its behavior unless it could actually see the definition copySomeInts
and not just the signature.
While the restrict
qualfiers would not imply that the function couldn't handle the case where source and destination overlap, it they would suggest that such handling would likely be more complicated than would be necessary without the qualifier. This would in turn suggest that in unless there is of explicit documentation promising to handle that case, the function should not be expected to handle it in defined fashion.
Note that in the case where the source and destination overlap, no storage is actually addressed using the src
pointer nor anything derived from it. If src+i==dest
or dest+i==src
, that would mean both src
and dest
identify elements of the same array, and thus src-dest
would simply represent the difference in their indices. The const and restrict qualifiers on src
mean that nothing which is accessed with a pointer derived from src
can be modified in any way during the execution of the function, but that restriction only applies to things that are accessed with pointers derived from src
. If nothing is actually accessed with such pointers, the restriction is vacuous.