What kind of optimization does const offer in C/C++?
Source
Case - 1:
When you declare a const in your program,
int const x = 2;
Compiler can optimize away this const by not providing storage to this variable rather add it in symbol table. So, subsequent read just need indirection into the symbol table rather than instructions to fetch value from memory.
Note: If you do something like:
const int x = 1;
const int* y = &x;
Then this would force compiler to allocate space for x
. So, that degree of optimization is not possible for this case.
In terms of function parameters const
means that parameter is not modified in the function. As far as I know, there's no substantial performance gain for using const
rather it's a means to ensure correctness.
Case - 2:
"Does declaring the parameter and/or the return value as const help the compiler to generate more optimal code?"
const Y& f( const X& x )
{
// ... do something with x and find a Y object ...
return someY;
}
What could the compiler do better? Could it avoid a copy of the parameter or the return value?
No, as argument is already passed by reference.
Could it put a copy of x or someY into read-only memory?
No, as both x
and someY
live outside its scope and come from and/or are given to the outside world. Even if someY
is dynamically allocated on the fly within f()
itself, it and its ownership are given up to the caller.
What about possible optimizations of code that appears inside the body of f()? Because of the const, could the compiler somehow improve the code it generates for the body of f()?
Even when you call a const member function, the compiler can't assume that the bits of object x
or object someY
won't be changed. Further, there are additional problems (unless the compiler performs global optimization): The compiler also may not know for sure that no other code might have a non-const reference that aliases the same object as x
and/or someY
, and whether any such non-const references to the same object might get used incidentally during the execution of f();
and the compiler may not even know whether the real objects, to which x
and someY
are merely references, were actually declared const in the first place.
Case - 3:
void f( const Z z )
{
// ...
}
Will there be any optimization in this?
Yes because the compiler knows that z
truly is a const object, it could perform some useful optimizations even without global analysis. For example, if the body of f()
contains a call like g( &z )
, the compiler can be sure that the non-mutable parts of z
do not change during the call to g()
.
Before giving any answer, I want to emphasize that the reason to use or not use const
really ought to be for program correctness and for clarity for other developers more so than for compiler optimizations; that is, making a parameter const
documents that the method will not modify that parameter, and making a member function const
documents that that member will not modify the object of which it is a member (at least not in a way that logically changes the output from any other const member function). Doing this, for example, allows developers to avoid making unnecessary copies of objects (because they don't have to worry that the original will be destroyed or modified) or to avoid unnecessary thread synchronization (e.g. by knowing that all threads merely read and do not mutate the object in question).
In terms of optimizations a compiler could make, at least in theory, albeit in an optimization mode that allows it to make certain non-standard assumptions that could break standard C++ code, consider:
for (int i = 0; i < obj.length(); ++i) {
f(obj);
}
Suppose the length
function is marked as const
but is actually an expensive operation (let's say it actually operates in O(n) time instead of O(1) time). If the function f
takes its parameter by const
reference, then the compiler could potentially optimize this loop to:
int cached_length = obj.length();
for (int i = 0; i < cached_length; ++i) {
f(obj);
}
... because the fact that the function f
does not modify the parameter guarantees that the length
function should return the same values each time given that the object has not changed. However, if f
is declared to take the parameter by a mutable reference, then length
would need to be recomputed on each iteration of the loop, as f
could have modified the object in a way to produce a change in the value.
As pointed out in the comments, this is assuming a number of additional caveats and would only be possible when invoking the compiler in a non-standard mode that allows it to make additional assumptions (such as that const
methods are strictly a function of their inputs and that optimizations can assume that code will never use const_cast
to convert a const reference parameter to a mutable reference).
Function parameters:
const
is not significant for referenced memory. It's like tying a hand behind the optimizer's back.
Suppose you call another function (e.g. void bar()
) in foo
which has no visible definition. The optimizer will have a restriction because it has no way of knowing whether or not bar
has modified the function parameter passed to foo
(e.g. via access to global memory). Potential to modify memory externally and aliasing introduce significant restrictions for optimizers in this area.
Although you did not ask, const
values for function parameters does allow optimizations because the optimizer is guaranteed a const
object. Of course, the cost to copy that parameter may be much higher than the optimizer's benefits.
See: http://www.gotw.ca/gotw/081.htm
Variable declarations:
const int i = 1234
This depends on where it is declared, when it is created, and the type. This category is largely where const
optimizations exist. It is undefined to modify a const
object or known constant, so the compiler is allowed to make some optimizations; it assumes you do not invoke undefined behavior and that introduces some guarantees.
const int A(10);
foo(A);
// compiler can assume A's not been modified by foo
Obviously, an optimizer can also identify variables which do not change:
for (int i(0), n(10); i < n; ++i) { // << n is not const
std::cout << i << ' ';
}
Function declarations:
const char* foo()
Not significant. The referenced memory may be modified externally. If the referenced variable returned by foo
is visible, then an optimizer could make an optimization, but that has nothing to do with the presence/absence of const
on the function's return type.
Again, a const
value or object is different:
extern const char foo[];
SomeClass* const pObj
creates a constant object of pointer type. There exists no safe method of changing such an object, so the compiler can, for example, cache it into a register with only one memory read, even if its address is taken.
The others don't enable any optimizations specifically, although the const
qualifier on the type will affect overload resolution and possibly result in different and faster functions being selected.
The exact effects of const differ for each context where it is used. If const is used while declaring an variable, it is physically const and potently resides in read-only memory.
const int x = 123;
Trying to cast the const-ness away is undefined behavour:
Even though const_cast may remove constness or volatility from any pointer or reference, using the resulting pointer or reference to write to an object that was declared const or to access an object that was declared volatile invokes undefined behavior. cppreference/const_cast
So in this case, the compiler may assume that the value of x
is always 123
. This opens some optimization potential (constants propagation)
For functions it's a different matter. Suppose:
void doFancyStuff(const MyObject& o);
our function doFancyStuff
may do any of the following things with o
.
- not modify the object.
- cast the constness away, then modify the object
- modify an
mutable
data member of MyObject
Note that if you call our function with an instance of MyObject that was declared as const, you'll invoke undefined behavior with #2.
Guru question: will the following invoke undefined behavior?
const int x = 1;
auto lam = [x]() mutable {const_cast<int&>(x) = 2;};
lam();