
Const and Optimization in C - adamnemecek
http://nullprogram.com/blog/2016/07/25/
======
nkurz
_The C99 specification, in §6.7.3¶5, has one sentence just for this:_

    
    
      If an attempt is made to modify an object defined with a   
      const-qualified type through use of an lvalue with non-
      const-qualified type, the behavior is undefined.
    

This is a separate question than whether the compiler can rely on the function
prototype, but doesn't his definition of bar() invoke undefined behavior by
this rule?

    
    
      void foo(const int *readonly_x) {
        int *x = (int *)readonly_x;  // cast away const
        (*x)++;
      }
    

I naively assumed that in the context of the bar() function, readonly_x is "an
object defined with a const-qualified type", and that since the function
modifies this "through use of an lvalue with non-const-qualified type", that
bar() invokes undefined behavior. Or am I falsely equating "declaring an
object" with "defining an object"?

 _The original x wasn’t const-qualified, so this rule didn’t apply. And there
aren’t any rules against casting away const to modify an object that isn’t
itself const._

Does the original x matter here, or just the readonly_x variable that is in
scope of the function? In the language of the spec, are x and readonly_x the
same object? Or two different objects of different types that happen to point
to the same address? Is it certain that const-qualifiers on function arguments
can be legally be ignored inside that function?

~~~
ynik
`readonly_x` is a non-constant object of type 'pointer to const int'. If
`readonly_x` itself was const-qualified, it'd look like this: `const int*
const readonly_x`.

This is a distinct object from whatever pointer the caller is passing in. But
none of this matters to the special const rule -- that rule is not talking
about the pointer object, but about the target object that the pointer is
pointing to.

~~~
nkurz
Thanks, that goes a long way toward an explanation. I'm still a bit surprised
though that no compilers seem to complain even if we cast away the const from
the properly const-qualified version. Here's the snippet I was playing with
that tests all the variations:
[https://godbolt.org/g/aaC4B7](https://godbolt.org/g/aaC4B7)

I should have expected it, but if you define the "lying" function where the
compiler can see the full definition (and don't specify -fno-inline), the
loads are optimized out. It made me wonder though whether some variation of
this bug might apply : [http://www.playingwithpointers.com/ipo-and-
derefinement.html](http://www.playingwithpointers.com/ipo-and-
derefinement.html)

~~~
nkurz
[Saw a response here, but it disappeared before my reply. Posted here anyway
in case it clarifies my first paragraph.]

Did you check out the link?
[https://godbolt.org/g/aaC4B7](https://godbolt.org/g/aaC4B7)

My surprise is that none of clang, gcc, or icc give any warning on this with
-Wall -Wextra:

    
    
      1  void copy_const(const int * const arg) {
      2    int *copy = (int *)arg;
      3    (*copy)++;
      4  }
    

I agree that the cast on line 2 is legal and requires no warning. My surprise
is that there is no warning for the write on line 3, since I think this is
undefined behavior according the quoted part of the spec. Isn't this trying
"to modify an object defined with a const-qualified type through use of an
lvalue with non-const-qualified type"?

I realize that the compiler has no obligation to issue a warning here, and in
fact is fully entitled to my first-born as soon as it encounters undefined
behavior. Still, it seems like it would be a useful place to offer the user a
warning just as it does in the case of direct modification:

    
    
      1 void write_const(const int * const arg) {
      2   (*arg)++;
      3 }
    

clang: "error: read-only variable is not assignable"

gcc: "error: increment of read-only location '*arg'"

icc: "error #137: expression must be a modifiable lvalue"

~~~
Too
In gcc try -Wcast-qual

~~~
nkurz
It's good to know that it exists (and that it's not included in -Wall -Wextra)
but it's not quite what I'm looking for. The cast itself is well defined ---
it's the subsequent write that is the issue. While it wouldn't be possible to
catch all of these, those that are caught are likely genuine bugs.

That said, it looks like -Wcast-qual is also supported by clang and icc. Clang
includes it in "-Weverything", which gcc and icc do not support. Even if not
ideal for this issue, I'm sure there are cases where it would help to catch
bugs.

------
WalterBright
One of the subtler distinctions between C and D is D does allow this
optimization. This occasionally engenders debate about which is better.

~~~
btrask
What is the argument in favor of specifically allowing casting away const of
pointers?

~~~
ridiculous_fish
The strongest argument is to support a sort of poor man's const-generics.
Consider a function like strchr(). This locates a character in a (const)
string, and returns a (non-const) pointer to it.

The idea is that you can use this on both const and non-const strings. Call it
on a const string, you get back a non-const pointer (which you better treat as
const!) But call it on a non-const string, you get back a non-const pointer,
with which you can mutate the string. So a single function serves both const
and non-const uses.

If casting away const-ness were disallowed, the compiler might conclude that
strchr()'s returned value cannot alias the input string, and its optimizations
would defeat this design. Anyways that's the original rationale.

~~~
mannykannot
It does not strike me as much of a benefit, and certainly not worth the cost
of creating confusing semantics (in fact, I would prefer (a true) const to be
the default in any language, with something like 'mutable' required to
override.)

------
x0x0
So if I understand the author, const function arguments can just have const-
ness cast away inside the function, so it's really much closer to a type hint
than anything else. However, if a variable is declared const, casting away
const-ness is undefined. Is this about right? Yikes that's complex. I suppose
the moral is casting away const-ness is a terrible idea.

~~~
kbart
_" casting away const-ness is undefined"_

No, it's not. That's the whole point why compiler can't optimize it away. You
can often see ("char *") casts from static "strings" in legacy APIs and
libraries calls, because original authors didn't know or didn't care how to
use const correctly (or at all). I, personally, use const a lot throughout my
C code, when you get it, it makes debugging so much easier.

~~~
x0x0
you skipped the first clause of my sentence --

if a variable is declared const, casting away const-ness is undefined

is that not true?

~~~
kbart
Your first part was irrelevant: if you cast away const, of course variable
must have been const in first place. As other have already mentioned, casting
itself is not an undefined behavior (and sometimes even have legitimate
reasons, mentioned in my earlier post). On the other hand, trying to _modify_
const variable though non-const pointer _is_ undefined behavior.

P.S. if you downvoted to disagree with this (on my parent post), please
provide an opposite example, because as a C programmer, I would really be
interested to be proven wrong on this matter.

~~~
x0x0
No, it could be mutable variable referenced/accessed (not sure of the right
word here) in a function via either a reference-to-const or a pointer-to-
const.

It seems highly non-intuitive to me that casting away constness works
differently depending on whether the variable was defined const or not, but if
that's the rules, that's the rules.

------
peeyek
The following is a foo function that take a constant pointer:

    
    
        void foo(int *const x);
    

If you point x to another adress inside foo function, it will not compiled.

The author seems think that

    
    
        void foo(cont int *x);
    
    

is function that takes a constant pointer which is wrong, it is a function
that takes pointer to constant object. In this case, it is legal if you point
x to another address in memory inside foo function.

~~~
heinrich5991
Where do you see this confusion? I don't think it's there.

The author points out that it is not necessarily undefined behavior to take
your second prototype, cast the const away and modify the pointed-to object
_if_ you ensure that it is only called with non-constant objects.

~~~
peeyek
In 2nd paragraph, the author states:

    
    
        void foo(const int *);
        ...
    

> The function foo takes a const pointer, which is a promise from the author
> of foo that it won’t modify the value of x. Given this information, it would
> seem the compiler may assume x is always zero, and therefore y is always
> zero.

~~~
Sharlin
It is clear that the author means "pointer to const" there.

------
QuadrupleA
I've personally stopped using const in my C and C++ code, except for actually
defining constants (in the #define sense). The headaches and complexity it
introduces don't seem worth the benefit - you inevitably have to remove the
const modifiers from your "const correct" function at some point when it needs
to calls out to a non-const function you don't control (one that you know is
'logically const' and pure but doesn't declare it as such in the signature).
And as this article demonstrates, the benefits to optimization are not always
that great.

And with any kind of STL-like interface or container you're suddenly
maintaining two duplicate versions of everything, a non-const version and a
const version, likely along with a confusing pile of template metaprogramming
and typedefs to support that. Avoiding const altogether is much cleaner.

As Casey Muratori (game programmer) once said, "I haven't typed "const" in
over a decade, and I have had literally zero bugs that it would have caught."

~~~
jjnoakes
I feel like this is analogous to throwing the baby out with the bath water.

I think writing const-correct code where you can is useful. It helps document
your code (for yourself and for others), it CAN catch issues (even if Casey
Muratori claims it never helped), and it really isn't that hard to do (C
routines don't need to be duplicated if you want to return non-const, like
strstr, for example).

So what if you have to cast-away-const for interfacing to libraries which
aren't const-correct? It still has benefits for your own code.

------
Too
Here is the gist of the explanation from the article: _And there aren’t any
rules against casting away const to modify an object that isn’t itself const.
This means the above (mis)behavior of foo isn’t undefined behavior for this
call. Notice how the undefined-ness of foo depends on how it was called._

Wtf? This seems like a huge potential optimization gain missed. Surely by know
it can't just be enabled because a lot of code would be written with the
assumption above, I'm just curious for the rationale of this model. I don't
understand the reasoning at all, why would you ever want to cast from a const
to non const and have the behaviour depend on the type I pass to the function?
Let's assume the signature of foo was faulty definied as const but still
modifies x inside (faulty definined signatures for API compatibility seems to
be the whole reason for allowing you to cast away const) and I pass int x, the
the behaviour is definied but if I pass a const int x the behaviour is
undefined?!? Clearly this is the fault of the person writing the foo function
but how does that help me writing the bar function, I am not expected to know
the whole call stack of foo, that's what the signature is for, I must be able
to trust it's const correctness and that should not depend on what I pass into
it.

If the optimization was _always_ disabled I could understand the reasoning but
now you get some kind of half assed middle ground where you as a caller must
guarantee the actions of a callee.

~~~
makapuf
Is there any portable way to tell the compiler it _can_ do this kind of
optimization in C ? Wouldn't that be useful ?

------
arm85
"void foo(const int * );"

"The function foo takes a const pointer".

I thought foo was taking a pointer a constant, where the pointer itself could
change. This may seem pedantic, but perhaps the optimization he expected would
have worked if foo took "const int * const ".

~~~
cygx
`const int * ` and `int const * ` are semantically identical, ie `const int
const * ` is a duplicate declaration.

~~~
arm85
Opps, I'll update my post. Looks like peeyek had already pointed out that the
pointer isn't const anyway.

