Переносимость ссылки на память - PullRequest
0 голосов
/ 17 апреля 2019

После прочтения о возможных способах повторного связывания ссылки в C ++, которая должна быть недопустимой , я нашел особенно уродливый способ сделать это.Причина, по которой я думаю, что ссылка действительно получает отскок, заключается в том, что она не изменяет исходное значение ссылки, но память самой ссылки.После еще нескольких исследований я обнаружил, что ссылка не гарантированно имеет память, но когда она есть, мы можем попытаться использовать код:

#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;
}

Пример вывода с использованием GCC8.1, но также проверено в MSVC:

эталонное значение 5 в памяти 0x7ffd1b4eb6b8

эталонное значение изменено на 6 в памяти 0x7ffd1b4eb6bc

пять значений по-прежнему5 в памяти 0x7ffd1b4eb6b8

Вопросы:

  1. Считается ли описанный выше метод неопределенным поведением?Почему?
  2. Можно ли с технической точки зрения сказать, что ссылка получает отскок, даже если она должна быть недопустимой?
  3. В практической ситуации, когда код уже работал с использованием определенного компилятора на определенной машине,является ли приведенный выше код переносимым (гарантированно работающим в каждой операционной системе и на каждом процессоре), если мы используем одну и ту же версию компилятора?

Ответы [ 3 ]

1 голос
/ 17 апреля 2019

Выше код имеет неопределенное поведение. Результат вашего reinterpret_cast<int**>(…) на самом деле не указывает на объект типа int*, но вы разыменовываете и перезаписываете сохраненное значение гипотетического int* объекта в этом месте, нарушая по крайней мере строгое правило псевдонимов в процессе [basic.lval] / 11 . На самом деле в этом месте даже нет объекта какого-либо типа (ссылки не являются объектами) & hellip;

В вашем коде привязана ровно одна ссылка, и это происходит, когда конструктор Reference инициализирует член r. Ни при каких условиях ссылка не может быть связана с другим объектом. Кажется, это просто работает из-за того факта, что компилятор реализует ваш ссылочный элемент через поле, в котором хранится адрес объекта, на который ссылается ссылка, и который находится в том месте, на которое указывает неверный указатель & hellip;

Кроме того, у меня возникли бы сомнения в том, допустимо ли для начала использовать offsetof в качестве базового члена. Даже если это так, эта часть вашего кода будет в лучшем случае условно поддерживаться с эффективным поведением, определяемым реализацией [support.types.layout] / 1 , поскольку ваш класс Reference не является классом стандартного макета [class.prop] /3.1 (имеет элемент ссылочного типа).

Поскольку ваш код имеет неопределенное поведение, он не может быть переносимым & hellip;

0 голосов
/ 18 апреля 2019

Ссылки могут быть восстановлены по закону, если вы прыгаете через правильные обручи:

#include <new>
#include <cassert>

struct ref {
  int& value;
};

void test() {
  int x = 1, y = 2;
  ref r{x};
  assert(&r.value == &x);
  // overwrite the memory of r with a new ref referring to y.
  ref* rebound_r_ptr = std::launder(new (&r) ref{y});
  // rebound_r_ptr points to r, but you really have to use it.
  // using r directly could give old value.
  assert(&rebound_r_ptr->value == &y);
}

Редактировать: Годболт ссылка .Вы можете сказать, что это работает, потому что функция всегда возвращает 1.

0 голосов
/ 17 апреля 2019

Как показано в другом ответе, ваш код имеет UB.Ссылка не может быть перенастроена - это зависит от языкового дизайна, и независимо от того, какую хитрость вы пытаетесь использовать, вы не можете обойти это, вы все равно получите UB.

Но вы можете сделать повторную привязкуссылочная семантика с 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
...