Consider the following code:
void doesnt_modify(const int *);
int foo(int *n) {
*n = 42;
doesnt_modify(n);
return *n;
}
where the definition of doesnt_modify
isn’t visible for the compiler. Thus, it must assume, that doesnt_modify
changes the object n
points to and must read *n
before the return
(the last line cannot be replaced by return 42;
).
Assume, doesnt_modify
doesn’t modify *n
. I thought about the following to allow the optimization:
int foo_r(int *n) {
*n = 42;
{ /* New scope is important, I think. */
const int *restrict n_restr = n;
doesnt_modify(n_restr);
return *n_restr;
}
}
This has the drawback that the caller of doesnt_modify
has to tell the compiler *n
isn’t modified, rather than that the function itself could tell the compiler via its prototype. Simply restrict
-qualifying the parameter to doesnt_modify
in the declaration doesn’t suffice, cf. “Is top-level volatile
or restrict
significant [...]?”.
When compiling with gcc -std=c99 -O3 -S
(or Clang with the same options), all functions are compiled to equivalent assembly, all re-reading the 42
from *n
.
Would a compiler be allowed to do this optimization (replace the last line by return 42;
) for foo_r
? If not, is there a (portable, if possible) way to tell the compiler doesnt_modify
doesn’t modify what its argument points to? Is there a way compilers do understand and make use of?
Does any function have UB (provided doesnt_modify
doesn’t modify its argument’s pointee)?
Why I think, restrict
could help here (From C11 (n1570) 6.7.3.1 “Formal definition of restrict
”, p4 [emph. mine]):
[In this case, B
is the inner block of foo_r
, P
is n_restr
, T
is const int
, and X
is the object denoted by *n
, I think.]
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:T
shall not be const-qualified. […]
$ clang --version
Ubuntu clang version 3.5.0-4ubuntu2 (tags/RELEASE_350/final) (based on LLVM 3.5.0)
Target: x86_64-pc-linux-gnu
Gcc version is 4.9.2, on an x86 32bit target.
Version 1 seems clearly specified by the formal definition of restrict
(C11 6.7.3.1). For the following code:
const int *restrict P = n;
doesnt_modify(P);
return *P;
the symbols used in 6.7.3.1 are:
P
*P
which is const int
P
*P
is what we're interested in6.7.3.1/4 (partial):
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:T
shall not be const-qualified [...] If these requirements are not met, then the behavior is undefined.
Note that T
is const-qualified. Therefore, if X
is modified in any way during this block (which includes during the call to a function in that block), the behaviour is undefined.
Therefore the compiler can optimize as if doesnt_modify
did not modify X
.
Version 2 is a bit more difficult for the compiler. 6.7.6.3/15 says that top-level qualifiers are not considered in prototype compatibility -- although they aren't ignored completely.
So although the prototype says:
void doesnt_modify2(const int *restrict p);
it could still be that the body of the function is declared as void doesnt_modify2(const int *p)
and therefore might modify *p
.
My conclusion is that if and only if the compiler can see the definition for doesnt_modify2
and confirm that p
is declared restrict
in the definition's parameter list then it would be able to perform the optimization.
Generally, restrict
means that the pointer is not aliased (i.e. only it or a pointer derived from it can be used to access the pointed-to object).
With const
, this means that the pointed-to object cannot be modified by well-formed code.
There is, however, nothing to stop the programmer breaking the rules using an explicit type conversion to remove the const
ness. Then the compiler (having been beaten into submission by the programmer) will permit an attempt to modify the pointed-to object without any complaint. This, strictly speaking, results in undefined behaviour so any result imaginable is then permitted including - possibly - modifying the pointed-to object.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With