Reputation: 2573
For example:
void f(const int&);
void g1() {
const int i = 42;
if (i == 42) f(i);
if (i == 42) f(i);
}
void g2() {
int i = 42;
if (i == 42) f(i);
if (i == 42) f(i);
}
It seems like "f" mutating its argument should be UB, and therefore compilers should be allowed to assume it doesn't happen and optimize accordingly. Yet these two functions will produce different assembly.
I don't have a copy of the standard. Is this not guaranteed?
Upvotes: 11
Views: 507
Reputation: 2687
If f()
has no side effect you could declare it with __attribute__((const))
:
int f(const int&) __attribute__((const));
Note that it doesn't make sense for a const
function to return void
, so I changed it to int
; and to avoid the compiler from optimising away the whole function call I assigned to an extern
:
int f(const int&) __attribute__((const));
extern int j;
void g1() {
const int i = 42;
if (i == 42) j = f(i);
if (i == 42) j = f(i);
}
void g2() {
int i = 42;
if (i == 42) j = f(i);
if (i == 42) j = f(i);
}
Now both g1()
and g2()
compile to a single call to f()
.
Upvotes: 1
Reputation: 275435
void foo( int const& x ) {
const_cast<int&>(x) = 7;
}
this is legal C++.
Invoking it with a reference to an actual const int
results in undefined behavior.
int x =42;
foo(x);
std::cout << x << "\n";
this must print "7\n"
.
int const x = 42;
f(x);
this program exhibits undefined behavior.
void g1() {
const int i = 42;
if (i == 42) f(i);
if (i == 42) f(i);
}
the compiler may assume i==42
is true
regardless of what f
does. There is no way defined in C++ to make a value read through i
anything other than 42
.
void g2() {
int i = 42;
if (i == 42) f(i);
if (i == 42) f(i);
}
the compiler must check if i==42
still after the call to f(const int&)
if it cannot examine the code within f(const int&)
(since it is defined and valid, even if surprising, behavior for f()
to change i
, and the compiler must respect this possibility). It can, however, optimize the first i==42
to true
, as there is no defined way for i
to be changed.
void g3() {
int j =34;
int i = 42;
f(j);
if (i == 42) f(i);
if (i == 42) f(i);
}
here, even though you could imagine that f
takes the address of j
, finds i
next to it, then modified i
, that is not defined behavior. There remains no way within the bounds of the standard to modify i
between initialization and the first i==42
check.
A lot of the reachability rules of C++ are about the compiler being permitted to assume that variables are only modified in certain code, and cannot be modified in others.
Going deeper down the rabbit hole:
struct foo {
int arr[3]={1,2,3};
int x = 42;
};
here:
foo bob;
f(bob.arr[0]);
this call to f
can modify all of bob.arr[0]
, bob.arr[1]
and bob.arr[2]
, but it cannot modify bob.x
. There is no way under the standard to get from an element of an array to a different member of the struct containing the array.
Note that code modifying things through const&
is extremely dangerous and a bad plan. But C++ allows it.
Upvotes: 2
Reputation: 11869
It's perfectly fine according to the standard to cast a pointer-to-const to pointer-to-non-const in C++ and then modify it (albeit it is confusing), as long the value the pointer points to wasn't declared as const
. In fact, C++ provides a keyword to do such a cast, const_cast
.
For instance, this is fine.
int a = 2;
const int* b = &a;
*const_cast<int*>(b) = 4;
But this isn't as a memory location to which pointer points to is const
.
const int a = 2;
const int* b = &a;
*const_cast<int*>(b) = 4;
Compiler in your example has to assume that a called function could possibly do that as it knows nothing about it, and prepare for such a situation.
Upvotes: 14
Reputation: 7838
A const
ref may alias the same memory as a non-const
variable that's modified in another thread. An interesting talk where this comes up (by an LLVM/Clang developer) is available here: https://www.youtube.com/watch?v=FnGCDLhaxKU
Upvotes: 4
Reputation: 180630
It seems like "f" mutating its argument should be UB
It is not, as long as it was not const
when you passed it to the function. It is only UB to modify something that was const
to begin with. So, f
could modify i
and you have to plan accordingly.
Upvotes: 6