Переосмысление состава void * не сделало то, что я ожидал

Я пытаюсь создать ссылку на структуру в произвольном блоке памяти, на который указывает указатель 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 17.06.2024 07:33

@ThreeStarProgrammer57 *t.p недействительно. Вы не можете разыменовать указатель void таким образом.

Weijun Zhou 17.06.2024 07:35

«Я ожидал, что ссылка будет такой же, как значение t.p.» значение ссылки — это значение исходного объекта, значение указателя — это адрес объекта в памяти. Это две совершенно разные ценности. Интерпретировать одно как другое не имеет смысла.

463035818_is_not_an_ai 17.06.2024 09:27

@WeijunZhou, ты прав, я думаю, это должен быть reinterpret_cast<It &>(*(It*)(t.p)), что выглядит довольно некрасиво. Другой вариант - написать auto& s = *reinterpret_cast<It *>(t.p);

ThreeStarProgrammer57 17.06.2024 10:54
t.p = &t.x;, поскольку t.x не является It, результатом будет неопределенное поведение. Приведения из указателя void* можно выполнить с помощью static_cast к указателю другого типа, но предостережение о том, что другой объект должен быть правильного типа для того, на что указывает, по-прежнему является обязательным.
Eljay 17.06.2024 14:11

@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 занимает

Remy Lebeau 17.06.2024 19:39

@RemyLebeau, ты прав, reinterpret_cast<It &> в моем случае ничего не делает, его можно удалить. Теперь что касается UB: если бы It был создан с новым размещением внутри x, это все равно было бы UB, чтобы разыменовать его, верно? Разве это не «псевдоним» x?

ThreeStarProgrammer57 18.06.2024 00:40

@ThreeStarProgrammer57, если t.p указывает прямо на t.x, то да. Но если t.p указывает на объект, возвращаемый placement-new, тогда UB не сможет привести и разыменовать t.p как It*.

Remy Lebeau 18.06.2024 01:12
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
9
119
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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), но попытка сделать эталонную штуку заставила меня задуматься!

Ashley Duncan 18.06.2024 01:44

@AshleyDuncan Готово.

HolyBlackCat 18.06.2024 05:23

Другие вопросы по теме