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
andb
are disjoint arrays, a call of the formh(100, a, b, b)
has defined behavior, because arrayb
is not modified within functionh
.
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 tofprintf
, except that the output is written into an array (specified by the arguments
) 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?
restrict
was introduced in C99.Since we do not know how the pointers will be accessed, we cannot know if a call will trigger undefined behavior
Yes. But this is a question of trust. A function declaration is a contract, between the programmer who wrote the function definition and programmer that uses the function. Remember, once in C we would just write
void f();
- here f is a function that takes an unspecified number of parameters. If you don't trust that programmer who wrote that function, no one will and don't use that functions. In C we are passing address of the first array element, so seeing a function declared like this, I would assume: the programmer who wrote that function gives some description on how these pointers are used or function f uses them as pointers to single element, not as arrays.( In times like this I like to use C99 VLAs in function declaration to specify how long arrays my function expects:
void f(int p[restrict 5], int q[restrict 10], int r[restrict 15]);
. Such function declaration is exactly equal to yours, but you have some idea what memory can't overlap. )char s[4]; sprintf(s, "%s", s);
will trigger undefined behavior?Yes. Copying takes place between objects that overlap and restrict location is accessed by two pointers.
Given a function signature like:
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.
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 definitioncopySomeInts
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. Ifsrc+i==dest
ordest+i==src
, that would mean bothsrc
anddest
identify elements of the same array, and thussrc-dest
would simply represent the difference in their indices. The const and restrict qualifiers onsrc
mean that nothing which is accessed with a pointer derived fromsrc
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 fromsrc
. If nothing is actually accessed with such pointers, the restriction is vacuous.The
restrict
ons
means that reads and writes only depends on whatsprintf()
does. The following code does that, reading and writing data pointed byp1
as thechar * restrict s
argument. Read/write only happened due to directsprintf()
code and not a side effect.Yet when
sprintf()
accesses the data pointed to byp2
, there is norestrict
. The “If copying takes place between objects that overlap, the behavior is undefined” applies top2
to sayp2
's data must not change due to some side effect.restrict
here is for the compiler to implement therestrict
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 ofrestrict
(new in C99), would not need that.C89 (pre-
restrict
days) also has this wording forstrcpy(), sprintf(), ...
and so may be simply a left-over over-spec in C99 forstrcpy()
.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 throughp
). Yet writing top
data is allowed mess up others - unless they have arestrict
.