LINUX.ORG.RU

std::map memcpy

 ,


0

2

Как скопировать std::map в память и потом вытащить ее оттуда?
Пример

std::map<std::string, std::any> _params;

uint8_t authorization_type = 77;
vector<uint8_t> password = { 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39 };

_params["authorization_type"] = authorization_type;
_params["password"] = password;

void* ptr = malloc(sizeof (_params));

memcpy(ptr, &_params, sizeof (_params));

Далее пытаюсь привести к указателю, получить значения мапа и получаю ошибку

std::map<std::string, std::any>* after_params = reinterpret_cast<std::map<std::string, std::any>*>(ptr);

uint8_t test = std::any_cast<uint8_t>((*after_params)["authorization_type"]);

std::map это не TriviallyCopyable тип, копировать его memcpy ­— UB.

Используй placement new:

void* ptr = malloc(sizeof (_params));
::new (ptr) std::map<std::string, std::any>(_params);
// ...
auto after_params = std::launder(reinterpret_cast<<std::string, std::any>*>(ptr));

utf8nowhere ★★ ()
Последнее исправление: utf8nowhere (всего исправлений: 5)

Не поленился и запустил, работает же:

#include <iostream>
#include <cstdlib>
#include <map>
#include <any>
#include <vector>
#include <cstring>

int main()
{
    std::map<std::string, std::any> _params;

uint8_t authorization_type = 77;
std::vector<uint8_t> password = { 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39 };

_params["authorization_type"] = authorization_type;
_params["password"] = password;

void* ptr = malloc(sizeof (_params));

memcpy(ptr, &_params, sizeof (_params));

    std::map<std::string, std::any>* after_params = reinterpret_cast<std::map<std::string, std::any>*>(ptr);

    uint8_t test = std::any_cast<uint8_t>((*after_params)["authorization_type"]);
    
    std::cout << (int)test;
}
Kronick ()
Ответ на: комментарий от utf8nowhere

Спасибо Вам большое! Все работает! Подскажите пожалуйста, где прочитать про это, почему нужно использовать placement new и std::launder ?

nzimvf ()
Ответ на: комментарий от Kronick

Работает потому что экземпляр мапа _params еще существует.

nzimvf ()
Ответ на: комментарий от Kronick

«Работает» ещё ничего не значит. Копировать таким образом контейнеры — гарантированный способ огрести проблем

XMs ★★★★★ ()
Ответ на: комментарий от nzimvf

почему нужно использовать placement new

Достаточно знать, что делает memcpy, чем оно отличается от placement new и как устроен std::map

XMs ★★★★★ ()
Ответ на: комментарий от XMs

10 программирую и только сегодня столкнулся с placement new.
Чем плох способ через кучу расшарить контейнер между потоками п оуказателю?

nzimvf ()

почитай про конструкторы копирования и операторы присваивания, тогда всё поймёшь.

а вообще тут плейсмент нью - оверинжиниринг, имхо.

делаешь просто auto *p = new YourMapType(sourceMapRef);

anonymous ()
Ответ на: комментарий от nzimvf

Как уже упомянули — не thread safe, если только ты не озаботишься этим самостоятельно. Непонятно, зачем делать так, если можно не жертвовать понятностью кода, создав указатель (желательно умный) на уже существующий экземпляр класса либо ссылку. В третьих — так ты шаришь не полностью, часть контейнера оказывается не шаренной, а скопированной. Ты знаешь, как устроен внутри std::map и чем placement new отличается от memcpy?

// А вообще выше верно анон написал — лучше дёрнуть конструктор копирования вместо placement new

XMs ★★★★★ ()
Последнее исправление: XMs (всего исправлений: 1)
Ответ на: комментарий от nzimvf

Любой контейнер, что внутри себя работает с кучей нельзя тягать через memcpy. Да блин, ты ж на плюсах пишешь! Специально для этого там есть перегрузка оператора присваивания. Что б для таких сложных типов как map не стрелять себе в ногу с копиованием памяти. Зачем ты используешь memcpy?

Aswed ★★★★★ ()
Ответ на: комментарий от XMs

«Работает» как раз-таки и значит что оно работает. Вопрос же ТСа в чем был? Что вот есть код, выдает ошибку. Запустил, ошибку не увидел.

Понятное дело что так делать не стоит, но вопрос не в этом был

Kronick ()
Ответ на: комментарий от Aswed

Есть смешанный код, который развивали годами.
Там была структура и указатель, через который можно было поработать.
Естественно, я хотел сделать попроще через мап и заиспользовав этот указатель, который, кстати говоря, везде в коде с ним работают через malloc()/free(), поэтому выделять контейнер в куче через new отпало желание, ибо в 100 разных местах по этому указателю делают free()

nzimvf ()
Ответ на: комментарий от anonymous

Естественно, я бы так и сделал. Но этот указатель живет в структуре и остальная часть n-ого кол-ва кода работает с этим указателем через malloc/free. Из-за этого использовать этот указатель через new/delete не хочется(потому что огребу огромное кол-во ошибок. тк в 100 разных местах для этого указателя делают free)

nzimvf ()
Ответ на: комментарий от Aswed

Проблема в том, что один и тот же указатель, который в основном используется через malloc/free. И проблема получается, что по одному указателю в одном случае придется делать free, в другом delete. И я никак эти случаи обработать не могу, т.к. не знаю, через что ранее была выделена память

nzimvf ()
Ответ на: комментарий от nzimvf

ну что тут сказать? могу только посоветовать провести рефакторинг, избавиться от ручного управления памятью, вместо этого пользоваться умными указателями

anonymous ()
Ответ на: комментарий от nzimvf

Ты пишешь на c++17. Используй shared_ptr или unique_ptr

Aswed ★★★★★ ()
Ответ на: комментарий от nzimvf

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

anonymous ()

капец вы тут все ударенные. Во-первых, если тебе пришла в голову такая идея, то ты желаешь что-то не то. Во-вторых: если тебе нужна значения, их которых ты потом хочешь получить мапу - сериализуй. В-третьих: Если мапа нужна в контексте одного процесса - то вообще накуй хер такие заморочки?

anonymous ()

Тут проблема не в map или memcpy, тут проблема в отсутствии понимания концепции инкапсуляции, которая является важнейшим принципом построения сложных систем.

std::map - состоит из 2-х основных архитектурных конструкций: интерфейса и реализации. Суть реализации лучше всего выражается понятием - чёрный ящик, там может находиться что угодно и твориться чёрт знает что. Поэтому пользователь (т.е. ты) работает только через предоставленный ему интерфейс. Если в данном интерфейсе есть методы для копирования - пользуйся. Всё просто.

anonymous ()
Ответ на: комментарий от ox55ff

И что теперь, на майдане скакать?

anonymous ()
Ответ на: комментарий от utf8nowhere

Я написал плохо. Не конкретно здесь, а вообще(описание на cppreference глянул, но въехать не получилось)

Deleted ()
Ответ на: комментарий от Deleted

У указателей есть значение. И это не адрес, а, скажем так, смысл указателя. Если был указатель на int, а его скастовали к указателю на float (через reinterpret_cast), то значение указателя не изменилось. Это по-прежнему указатель на int.

(значение указателя это не только указатель на объект какого-то типа)

(Вообще мне сложно сказать, какое значение у указателя, который возвращается malloc-ом.)

С помощью reinterpret_cast можно получать значения из указателя на тип T указатель на тип U, только если эти типы являются pointer-interconvertible, иначе значение указателя не изменяется. Получить указатель со значением «указатель на U» можно std::launder-ом. Естественно, при соблюдении соответствующих условий на отмываемый указатель.

utf8nowhere ★★ ()
Последнее исправление: utf8nowhere (всего исправлений: 1)
Ответ на: комментарий от Deleted

Не конкретно здесь, а вообще(описание на cppreference глянул, но въехать не получилось)

Как я понял, там скорее всего как с std::atomic — больше указание компилятору нежели реальные действия.

Вот например:

struct X {
  const int n; // note: X has a const member
  int m;
};

  X *p = new X{3, 4};
  const int a = p->n;
  X* np = new (p) X{5, 6};
  const int b = p->n; // undefined behavior 

Вот тут компилтор может загрузить значение p->n до того, как будет прозиведено placement-new. В результате b может быть 3 вместо ожидаемого 4.

std::launder(p) — ставит «барьер» и заставляет компилятор реально посмореть что же находится под p.

Т.е. с каждым поколением С++ оптимализатор становится все злее и агрессивнее.

KennyMinigun ★★★★★ ()
Последнее исправление: KennyMinigun (всего исправлений: 1)
Ответ на: комментарий от KennyMinigun
template <class _Tp>
_LIBCPP_NODISCARD_AFTER_CXX17 inline
_LIBCPP_CONSTEXPR _Tp* __launder(_Tp* __p) _NOEXCEPT
{
    static_assert (!(is_function<_Tp>::value), "can't launder functions" );
    static_assert (!(is_same<void, typename remove_cv<_Tp>::type>::value), "can't launder cv-void" );
#ifdef _LIBCPP_COMPILER_HAS_BUILTIN_LAUNDER
    return __builtin_launder(__p);
#else
    return __p;
#endif
}


#if _LIBCPP_STD_VER > 14
template <class _Tp>
_LIBCPP_NODISCARD_AFTER_CXX17 inline _LIBCPP_INLINE_VISIBILITY
constexpr _Tp* launder(_Tp* __p) noexcept
{
    return _VSTD::__launder(__p);
}
#endif
anonymous ()
Ответ на: комментарий от KennyMinigun

в фоли оказалось более понятнее чем в шланге

template <typename T>
FOLLY_NODISCARD inline T* launder(T* in) noexcept {
#if FOLLY_HAS_BUILTIN(__builtin_launder) || __GNUC__ >= 7
  // The builtin has no unwanted side-effects.
  return __builtin_launder(in);
#elif __GNUC__
  // This inline assembler block declares that `in` is an input and an output,
  // so the compiler has to assume that it has been changed inside the block.
  __asm__("" : "+r"(in));
  return in;
#elif defined(_WIN32)
  // MSVC does not currently have optimizations around const members of structs.
  // _ReadWriteBarrier() will prevent compiler reordering memory accesses.
  _ReadWriteBarrier();
  return in;
#else
  static_assert(
      false, "folly::launder is not implemented for this environment");
#endif
}

anonymous ()
Ответ на: комментарий от KennyMinigun

да на самом деле полный барьер памяти - если по простому

anonymous ()
Ответ на: комментарий от KennyMinigun

это только в случае cv квалификаторов компилятор в переменную b запишет 3 вместо ожидаемого 4 до placement-new или во всех случаях?

nzimvf ()
Ответ на: комментарий от utf8nowhere

(Вообще мне сложно сказать, какое значение у указателя, который возвращается malloc-ом.)

никакое. эффективный тип будет определен при первой записи. и уже потом кастовать этот указатель как попало не получится

tyro33 ()
Ответ на: комментарий от tyro33

глупость какую то написали
берем указатель на воид, маллочим с запасом 12 байт
кастуем к флоату и пишем туда флоат
кастуем к даблу и пишем туда дабл
кастуем к инту и читаем из него черт знает что
и вы утверждаете что там по прежнему будет флоат ?

anonymous ()
Ответ на: комментарий от KennyMinigun

больше указание компилятору нежели реальные действия

Да, я тоже так понял. Но возможные оптимизации. на мой взгляд, покрывают осложнения

Deleted ()
Ответ на: комментарий от anonymous

кастуем к инту и читаем из него черт знает что

UB

и вы утверждаете что там по прежнему будет флоат ?

Начиная с c++17

Deleted ()
Ответ на: комментарий от anonymous

после приведения void* к float* в понимании компилятора это будет память, выделенная под float*.
стандарт не гарантирует, что вы сможете привести и разыменовать эту память на любой другой тип данных. в таком коде нарушение strict aliasing и как следствие ub

tyro33 ()
Ответ на: комментарий от tyro33

А если все приведения будут внутри функций, принимающих void*?

yyk ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.