Const and Optimization in C
nullprogram.com
nullprogram.com
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?
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.
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
Did you check out the link? 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"
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.
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.
- Compatibility with old libraries
D has an `inout` qualifier that has the effect of "transmitting" the const-ness of an argument to the return type.
If you have "const int x" (as a local variable, global variable, or function parameter) then there is no valid way to modify x's value. But if you have "const int* y" (as a local variable, global variable, or function parameter), you can always cast away const and mutate as long as the original variable (the one "y" points to) wasn't declared const.
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.
if a variable is declared const, casting away const-ness is undefined
is that not true?
It's completely valid to use a non-const pointer to a const variable for reading that variable (this is actually quite common when interacting with libraries that are not const-correct). Undefined behavior only occurs when the const variable is being modified.
So if you cast away constness to conform to some API and then read the value, everything is fine (aside from questionable API design, of course). Modifying the value is another story, though.
This is important because older libs that ignore (or abuse) const will often do read-only access to a char* or something. A nice example is that POSIX defines
int execv(const char *path, char *const argv[]);
That definition takes a constant pointer to variable chars! Worse, some people use the same signature for `main()` and also for libraries that parse `argv`. But none of them are actually allowed to vary those chars under Unix.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.
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.
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.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.
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.Note that in contrast, restrict-qualifying a ponter-to-const (ie `const int *restrict x`) does make such guarantees, but only callee-side.
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."
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.
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.
"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 ".