Portability of memory reference rebinding

时间:2019-04-16 22:07:55

标签: c++ reference portability

After reading about possible ways of rebinding a reference in C++, which should be illegal, I found a particularly ugly way of doing it. The reason I think the reference really gets rebound is because it does not modify the original referenced value, but the memory of the reference itself. After some more researching, I found a reference is not guaranteed to have memory, but when it does have, we can try to use the code:

#include <iostream>
using namespace std;

template<class T>
class Reference
{
public:
    T &r;

    Reference(T &r) : r(r) {}
};

int main(void)
{
    int five = 5, six = 6;

    Reference<int> reference(five);

    cout << "reference value is " << reference.r << " at memory " << &reference.r << endl;

    // Used offsetof macro for simplicity, even though its support is conditional in C++ as warned by GCC. Anyway, the macro can be hard-coded
    *(reinterpret_cast<int**>(reinterpret_cast<char*>(&reference) + offsetof(Reference<int>, r))) = &six;

    cout << "reference value changed to " << reference.r << " at memory " << &reference.r << endl;

    // The value of five still exists in memory and remains untouched
    cout << "five value is still " << five << " at memory " << &five << endl;
}

A sample output using GCC 8.1, but also tested in MSVC, is:

reference value is 5 at memory 0x7ffd1b4eb6b8

reference value changed to 6 at memory 0x7ffd1b4eb6bc

five value is still 5 at memory 0x7ffd1b4eb6b8

The questions are:

  1. Is the method above considered undefined behavior? Why?
  2. Can we technically say the reference gets rebound, even though it should be illegal?
  3. In a practical situation, when the code has already worked using a specific compiler in a specific machine, is the code above portable (guaranteed to work in every operational system and every processor), assuming we use the same compiler version?

3 个答案:

答案 0 :(得分:1)

Above code has undefined behavior. The result of your reinterpret_cast<int**>(…) does not actually point to an object of type int*, yet you dereference and overwrite the stored value of the hypothetical int* object at that location, violating at least the strict aliasing rule in the process [basic.lval]/11. In reality, there is not even an object of any type at that location (references are not objects)…

Exactly one reference is being bound in your code and that happens when the constructor of Reference initializes the member r. At no point is a reference being rebound to another object. This simply appears to work due to the fact that the compiler happens to implement your reference member via a field that stores the address of the object the reference is refering to, which happens to be located at the location your invalid pointer happens to point to…

Apart from that, I would have my doubts whether it's even legal to use offsetof on a reference member to begin with. Even if it is, that part of your code would at best be conditionally-supported with effectively implementation-defined behavior [support.types.layout]/1, since your class Reference is not a standard-layout class [class.prop]/3.1 (it has a member of reference type).

Since your code has undefined behavior, it cannot possibly be portable…

答案 1 :(得分:0)

As shown in the other answer, your code has UB. A reference cannot be re-boud - this is by language design and no matter what kind of casting trickery you try you cannot get around that, you will still end up with UB.

But you can have re-binding reference semantics with std::reference_wrapper:

int a = 24;
int b = 11;

auto r = std::ref(a); // bind r to a

r.get() = 5; // a is changed to 5

r = b; // re-bind r to b

r.get() = 13; // b is changed to 13

答案 2 :(得分:0)

如果您跳过正确的圈,则引用可以合法地反弹:

       itemBuilder: (BuildContext context, int index) {
            return Text("Hoi"); 
          },

编辑:godbolt link。您可以说它起作用,因为该函数总是返回1。