Я пытаюсь создать ссылку на структуру в произвольном блоке памяти, на который указывает указатель void. Но я, похоже, не могу с этим справиться!
Учитывая приведенный ниже пример кода (последний протестирован на clang), я обнаружил, что моя ссылка инициализируется по адресу самого указателя void, а не по значению указателя void. Пожалуйста, помогите мне понять, чего я не понимаю!
#include <iostream>
int main(void)
{
struct Test
{
int a;
void * p;
unsigned x[10];
};
struct It
{
unsigned y[10];
};
Test t;
t.p = &t.x;
auto & s = reinterpret_cast<It &>(t.p);
std::cout << std::hex << "&t = " << &t << ", &t.p = " << &t.p << ", p = " << t.p << ", s = " << (void*)&s << std::endl;
}
Я получаю результат:
&t=0x7ffd4fbac9f8, &t.p=0x7ffd4fbaca00, p=0x7ffd4fbaca08, s=0x7ffd4fbaca00
Как видите ссылки на память по адресу т.п. Я ожидал, что ссылка будет такой же, как значение t.p.
Чтобы расширить это дальше, результат будет другим, если вы измените линию приведения на:
auto a = t.p;
auto & s = reinterpret_cast<It &>(a);
Тогда вы получаете:
&t=0x7ffc31e65578, &t.p=0x7ffc31e65580, p=0x7ffc31e65588, s=0x7ffc31e65570
Где ссылка s установлена на адрес t.
Это скорее учебный вопрос, чем что-либо еще, но было бы полезно понять его на будущее.
reinterpret_cast<It &>(t.p) будет интерпретировать t.p, как если бы это была ссылка, но это указатель. Если я правильно понимаю, вам нужна ссылка на вещь, на которую указывает т.п. Итак, вы хотите reinterpret_cast<It &>(*t.p)
@ThreeStarProgrammer57 *t.p
недействительно. Вы не можете разыменовать указатель void таким образом.
«Я ожидал, что ссылка будет такой же, как значение t.p.» значение ссылки — это значение исходного объекта, значение указателя — это адрес объекта в памяти. Это две совершенно разные ценности. Интерпретировать одно как другое не имеет смысла.
@WeijunZhou, ты прав, я думаю, это должен быть reinterpret_cast<It &>(*(It*)(t.p))
, что выглядит довольно некрасиво. Другой вариант - написать auto& s = *reinterpret_cast<It *>(t.p);
t.p = &t.x;
, поскольку t.x
не является It
, результатом будет неопределенное поведение. Приведения из указателя void*
можно выполнить с помощью static_cast
к указателю другого типа, но предостережение о том, что другой объект должен быть правильного типа для того, на что указывает, по-прежнему является обязательным.
@ThreeStarProgrammer57 Если вы приведете t.p
к It*
, вам не нужно будет использовать reinterpret_cast
при разыменовании указателя на тот же тип: auto &s = *(It*)(t.p);
или вместо этого использовать static_cast
: auto &s = *static_cast<It*>(t.p);
Но то, что сказал Элджей, верно, это все еще UB, поскольку t.p
не указывает для начала к объекту It
. Вам нужно будет изменить t.x
, чтобы он стал объектом It
вместо массива unsigned[]
. Или же используйте placement-new
, чтобы создать объект It
внутри памяти, которую t.x
занимает
@RemyLebeau, ты прав, reinterpret_cast<It &>
в моем случае ничего не делает, его можно удалить. Теперь что касается UB: если бы It
был создан с новым размещением внутри x
, это все равно было бы UB, чтобы разыменовать его, верно? Разве это не «псевдоним» x
?
@ThreeStarProgrammer57, если t.p
указывает прямо на t.x
, то да. Но если t.p
указывает на объект, возвращаемый placement-new
, тогда UB не сможет привести и разыменовать t.p
как It*
.
reinterpret_cast<T &>(x)
эквивалентен*reinterpret_cast<T *>(&x)
(за исключением того, что первый не учитывает перегруженный &
, что делает его полезным для реализации std::addressof
).
Итак, если у вас есть другой тип указателя, который можно разыменовать, вы можете сделать reinterpret_cast<It &>(*t.p)
. Но поскольку void *
нельзя разыменовать, ваш единственный вариант — *reinterpret_cast<It *>(t.p)
.
Будьте добры, если бы вы могли добавить ссылку на то, откуда берется «reinterpret_cast<T &>(x) эквивалентно *reinterpret_cast<T *>(&x)», чтобы сделать ответ полным. К вашему сведению, на практике я только что выполнил *reinterpret_cast<It *>(t.p), но попытка сделать эталонную штуку заставила меня задуматься!
@AshleyDuncan Готово.