There is a common misconception among C and C++ programmers about the value of using const, and more specifically its role in code optimizations.
Let's look at an example:
void bar( int const * a );
void
foo( int const * a )
{
printf( "Before bar(): %dn", *a );
bar(a);
printf( "After bar(): %dn", *a );
}
Many programmers think that because a points to a const int, the value it points is guaranteed to be the same before and after the call to bar(), and so a smart compiler could cache it in a register before calling bar(), and use the cached value in the second printf.
This is not so. Consider the complete program below:
#include <stdio.h>
void bar( int const * a );
void
foo( int const * a )
{
printf( "Before bar(): %dn", *a );
bar(a);
printf( "After bar(): %dn", *a );
}
int count;
void
bar( int const * a )
{
printf( "In bar(): %dn", *a );
++count;
}
int
main()
{
foo(&count);
}
I'm sure it's obvious that this program will print:
Before bar(): 0
In bar(): 0
After bar(): 1
Don't get me wrong, if bar() didn't increment the global variable count, or if main() didn't pass a pointer to count to foo(), the compiler is indeed allowed to be smart and cache the value of *a in foo() -- but it could do this regardless of whether foo() and/or bar() take int * or int const * parameter.
This matter is not to be confused with the situation when const is applied to an actual object, as in:
void bar( int const * a );
int
foo()
{
int const c=42;
printf( "%dn", c );
bar(&c);
printf( "%dn", c );
}
In this case, the compiler is allowed to save the value of c before calling bar(), then use the saved value in the second call to printf. It is allowed to do this even if bar was defined as:
void
bar( int const * a )
{
int * b=(int *)a; //or int * b=const_cast<int *>(a) in c++
++(*b);
}
The cast in bar is guaranteed to work only if the object pointed by a is not const. But this has nothing to do with the const qualifiers of a itself, it has to do with the const qualifier of c in foo().
Note that if you put the above foo() and bar() in a program, most likely you won't get compile errors. Still, the program is ill formed and has undefined behavior. This means that it can do anything at all, including crash when bar() attempts to increment the const object c, or send a nasty email to your boss. :)
Finally, I'll point out another useful detail about const: it is ignored in function declarations when applied to the parameters of the function. For example, all 3 declarations below declare the same function (with the same signature, in C++ terms):
void foo( int x );
void foo( int const x );
void foo( const int x );
That's because parameters are always passed by value and so foo() can't modify the caller's copy anyway. The fact that const is ignored in such declarations means that the implementer of foo() is free to use const in the definition of foo() or not, without having to change the declaration (which typically is in a header file) to match.
gemicha — 22 March 2009, 22:41
A smart compiler wouldn't cache it but a smart linker would cache it. If you use LTGC when the code generation happens everything is clear and many aliasing conflicts can be resolved.
A smart linker may use a register even if you skip const but I would keep it for LCTG and more importantly for anyone else who reads the code.
Emil — 23 March 2009, 09:28
I'm not advocating not using const. :)
All I'm saying is that const, when used to indicate that a reference or pointer can not be used to modify the target object, does not help code optimizations.
This holds true regardless of whether code generation happens at compile time or at link time.
Justinas V.D. — 01 April 2009, 13:16
void foo( int x ); and void foo( const int x ); are not exactly the same thing, and the statement
"Finally, I'll point out another useful detail about const: it is ignored in function declarations when applied to the parameters of the function. For example, all 3 declarations below declare the same function (with the same signature, in C++ terms) <..>"
is subtly incorrect. It is correct in that regard that, e.g., foo(5) call would possibly match any of those declarations, and compiler would complain about ambiguity. The difference, however, is quite big if one considers full definition, i.e., implementation of those functions.
void foo( int x ) {/**/} holds the copy of x in the stack AND allows to modify x's value, which sometimes is desirable and at other times it may lead to subtle bugs.
void foo( int const x ) and void foo( const int x ) declarations are identical, but they are different from the previous declaration in that regard that the implementer is not allowed to modify x's value, which is supposed to be less error prone is some contexts.
At any rate, personally, I would suggest using consts everywhere, except where constness makes no sense. :-)
Emil — 01 April 2009, 15:46
Nope, any one of:
void foo( int const );
void foo( const int );
void foo( int );
declares the same function, the compiler makes no distinction between those declarations. Calling foo(42) is not ambiguous because those are not different overloads, you're declaring the same thing 3 times. Definitions are also considered the same, check it out: http://codepad.org/Tjwxnunv.
My point was that the constness of the argument in the header doesn't have to match the constness of the argument in the cpp. So you should always use the void foo( int ) declaration in the header, while choosing to use const or not in the definition as appropriate.
Justinas V.D. — 02 April 2009, 05:17
I probably didn't make myself clear in my previous post; this might have stemmed from my less-than-efficient command of English as well.
I did not argue the validity of your statement about the relationship between declaration and definition, I merely pointed out that /semantics/ of those definitions are different -- albeit the compiler might treat those declarations as equivalent -- and so might differ the implementation of those functions.
Other than that, I believe we fully agree with each other and the compiler. :-D
Formatting hint: when posting comments, surround code blocks in [@ and @].