Are compilers not allowed to assume const-ref parameters will stay const?

纵饮孤独 提交于 2019-12-08 15:40:17

问题


For example:

https://godbolt.org/g/5eUkrx

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?


回答1:


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.




回答2:


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.




回答3:


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




回答4:


No

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.




回答5:


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().



来源:https://stackoverflow.com/questions/49117489/are-compilers-not-allowed-to-assume-const-ref-parameters-will-stay-const

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!