LINUX.ORG.RU

reinterpret_cast UB или нет?

 


1

5

Это UB? Есть ли возмодность сделать аналогичное без UB? Если это UB то по каким причинам?

template<typename K, typename V>
struct CPair {
  K key;
  V value;
};
int main()
{
  std::map<int, std::string> sp {{42, "test"}};
  CPair<int, std::string> *p =
  reinterpret_cast<CPair<int, std::string>*> (&(*sp.find(42)));
  std::cout << p->key << " " << p-> value;
}

★★★★★

Последнее исправление: invy (всего исправлений: 1)

Find вернёт итератор, а стандарт не гарантирует, что его можно кастить к указателю, так что это даже не ub, а полная херня.

DELIRIUM ☆☆☆☆☆
()

Есть ли возмодность сделать аналогичное без UB?

Добавить конструктор, принимающий std::pair, и сделать что-то вроде такого:

CPair<int, std::string> p(*sp.find(42));

Это не полная эквивалентность тому, что написано у тебя, но так более безопасно

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

Это?

  1. Any object pointer type T1* can be converted to another object pointer type cv T2*. This is exactly equivalent to static_cast<cv T2*>(static_cast<cv void*>(expression)) (which implies that if T2’s alignment requirement is not stricter than T1’s, the value of the pointer does not change and conversion of the resulting pointer back to its original type yields the original value). In any case, the resulting pointer may only be dereferenced safely if allowed by the type aliasing rules (see below)
invy ★★★★★
() автор топика
Ответ на: комментарий от DELIRIUM

Это другой вопрос. Тема про UB и если да, то почему? (На сишечке такое везде делают, то что это верный способ стрельнуть себе в ногу и так ясно)

invy ★★★★★
() автор топика

Вот тут не совсем ясно, что значит similar.

Type aliasing

Whenever an attempt is made to read or modify the stored value of an object of type DynamicType through a glvalue of type AliasedType, the behavior is undefined unless one of the following is true:

AliasedType and DynamicType are similar.
AliasedType is the (possibly cv-qualified) signed or unsigned variant of DynamicType.
AliasedType is std::byte, (since C++17)char, or unsigned char: this permits examination of the object representation of any object as an array of bytes. 

Informally, two types are similar if, ignoring top-level cv-qualification:

they are the same type; or
they are both pointers, and the pointed-to types are similar; or
they are both pointers to member of the same class, and the types of the pointed-to members are similar; or
they are both arrays of the same size or both arrays of unknown bound, and the array element types are similar. 
invy ★★★★★
() автор топика

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

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

[code]

template<typename K, typename V>

struct CPair {

K key;

V value;

};

int main()

{

std::map<int, std::string> sp {{42, «test»}};

auto n = sp.find(42);

CPair<int, std::string> f{n->first, n->second};

std::cout << f.key << " " << f.value;

} [/code]

Прошу прощения за качество, как нынче код-то вставлять? Почему игнорируется [code]?

Kronick
()

По пунктам:

  1. Каст указателя - не UB.
  2. Разыменование указателя (доступ к члену класса, в твоем примере) - UB, потому что не выполняются type aliasing rules.
roof ★★
()

Есть ли возмодность сделать аналогичное без UB?

Непонятно, что ты пытаешься сделать. Если получить свою структуру из результата поиска по мапу, то, как выше сказали, напиши соответствующий конструктор.

roof ★★
()

UB, не ленись и проверяй сам.

fsb4000@orangepilite2:~$ clang++  main.cpp -O3 -fsanitize=undefined -fsanitize=memory
fsb4000@orangepilite2:~$ ./a.out 
42 test
==2354==WARNING: MemorySanitizer: use-of-uninitialized-value
    #0 0xaaaaea375b3c in std::_Rb_tree<int, std::pair<int const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::_Select1st<std::pair<int const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >, std::less<int>, std::allocator<std::pair<int const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >::_M_erase(std::_Rb_tree_node<std::pair<int const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >*) (/home/fsb4000/a.out+0x97b3c)
    #1 0xaaaaea375a88 in std::_Rb_tree<int, std::pair<int const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::_Select1st<std::pair<int const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >, std::less<int>, std::allocator<std::pair<int const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >::_M_erase(std::_Rb_tree_node<std::pair<int const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >*) (/home/fsb4000/a.out+0x97a88)
    #2 0xaaaaea375888 in std::_Rb_tree<int, std::pair<int const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::_Select1st<std::pair<int const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >, std::less<int>, std::allocator<std::pair<int const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >::~_Rb_tree() (/home/fsb4000/a.out+0x97888)
    #3 0xaaaaea375284 in main (/home/fsb4000/a.out+0x97284)
    #4 0xffff94e74d20 in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x20d20)
    #5 0xaaaaea305e3c in _start (/home/fsb4000/a.out+0x27e3c)

SUMMARY: MemorySanitizer: use-of-uninitialized-value (/home/fsb4000/a.out+0x97b3c) in std::_Rb_tree<int, std::pair<int const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::_Select1st<std::pair<int const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >, std::less<int>, std::allocator<std::pair<int const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >::_M_erase(std::_Rb_tree_node<std::pair<int const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >*)
Exiting
fsb4000@orangepilite2:~$ 

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

на сишечке тупо указатель на воид и никаких итераторов.

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

deep-purple ★★★★★
()
Ответ на: комментарий от invy

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

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

deep-purple ★★★★★
()
Ответ на: комментарий от invy

А на основании чего так можно сказать? В описании с cppreferenсe выше ничего не подходит под ваш случай. Ни ваша структура, ни std::pair не являются ни указателями ни массивами. В стандарт сейчас не полезу, может, вечером.

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

Гг проблема тут в том что конечно же содержимого итератора уже нету, само собой указатель указывает вникуда.

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

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

deep-purple ★★★★★
()
Ответ на: комментарий от invy

Informally, two types are similar if, ignoring top-level cv-qualification:

  • they are the same type; or
  • they are both pointers, and the pointed-to types are similar; or
  • they are both pointers to member of the same class, and the types of the pointed-to members are similar; or
  • they are both arrays of the same size or both arrays of unknown bound, and the array element types are similar.

Определение рекурсивное. Относится как к указателям, так и к типам, которые получаются при их разывменовании.

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

Да, я ошибся, нужно использовать libcxx скомпилированную для msan

Подробнее: https://github.com/google/sanitizers/wiki/MemorySanitizerLibcxxHowTo

Вот так правильно:

fsb4000@orangepilite2:~$ clang++ ${MSAN_CFLAGS} main.cpp -Wl,-rpath,/home/fsb4000/llvm/libcxx_msan/lib
fsb4000@orangepilite2:~$ ./a.out 
42 test

Ничего не ругается на твой код. AddressSanitizer и UBSanitizer тоже не ругаются, вывод ожидаемый, тогда мне кажется всё норм, нету тут UB.

fsb4000 ★★★★★
()

Это сложный вопрос, на который нет однозначного ответа.

В выражении вида E1.E2 стандарт явно не требует чтобы выражение E1 типа T обозначало объект с типом T (или наследующимся от T). Было бы такое явное требование — жизнь стала бы намного проще.

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

Разве не так?

Не так. Доступ (access) определён как чтение или запись значения объекта (https://timsong-cpp.github.io/cppwp/n4659/defns.access#:access). glvalue, про доступ через которое говорится в т.н. strict aliasing rules, это именно то glvalue, непосредственно через которое делается чтение или запись (к которому применяется lvalue-to-rvalue conversion или которое стоит слева в (compound) assignment операторе, соответственно).

Т.е. когда происходит чтение из data member-а в выражении вида E1.E2, то strict aliasing rules требуют только определённого соответствия между типом всего выражения E1.E2 и типом member subobject-а, значение которого читается.

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

А в каких случаях тип всего выражения E1.E2 может не совпадать с типом subobject’a, который читается? Мне кажется, вы что-то пропустили.

roof ★★
()

Я бы немного упростил постановку вопроса: можно ли приводить указатель на одну структуру к указателю на другую структуру, но при этом определенную почти точно так же. Я бы так не делал. Может это и работает, но это плохая нечитаемая архитектура и нет задачи, где без этого нельзя было бы обойтись. А если уже так надо, то можно добавить конструктор (как тебе советовали) и тогда не придется использовать reinterpret_cast и код будет читаемым.

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

А в каких случаях тип всего выражения E1.E2 может не совпадать с типом subobject’a, который читается?

Ну например в таких, когда E1 не ссылается на объект правильного типа.

struct S1 { int m; };
struct S2 { float m; };
S1 s1{};
reinterpret_cast<S2*>(&s1)->m; // тут
anonymous
()
Ответ на: комментарий от anonymous

Хм, какой смысл говорить о каком-то там subobject’e в старой структуре, если мы скастили указатель reinterpret’ом?

struct A
{
  char a;
  char b;
  char c;
};

struct B
{
  short a;
  short b;
};

A a{};

reinterpret_cast<B*>(&a)->b; // какой тут тип "subobject’a, который читается"?
roof ★★
()
Ответ на: комментарий от roof

какой тут тип «subobject’a, который читается»?

Какая разница, если тип у reinterpret_cast<B*>(&a)->b это short, а никакого объекта типа short это выражение не обозначает?

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

Можно еще раз, помедленнее?

никакого объекта типа short это выражение не обозначает?

Кто на ком стоял?

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

Можно еще раз, помедленнее?

У меня сейчас нет особо времени и желания просвещать ньюфагов. Я, используя определения доступа (access), попытался тебе намекнуть (и объяснить) почему твои слова

у него доступ к pair через lvalue типа CPair

не верны. Могу просто дать тебе ссылку на Note из текущего драфта из которой явно написано почему то, что ты написал неверно. http://eel.is/c++draft/basic.lval#11.note-1: «Unlike in C, C++ has no accesses of class type.»

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

Именно. Вопрос исключительно ради академическо интереса и повышения понимания. В принципе касты вида

struct Base { int base; };
struct Derived { int base; int derived; };
struct Derived1 { int base; void f(); };
Derived derived;
Derived1 d1;
Base* baseptr = reinterpret_cast<Base*>(&derived);
baseptr = reinterpret_cast<Base*>(&d1);
auto i = baseptr->base;
baseptr->base = 42;

Сишники обожают.

Если назвать члены структуры по разному - это не должно сломать код.

Вот если добавить методы в Derived - появлчются вопросы. И какие условия должны соблюдаться чтобы не воявилось UB. Поможет ли использования тайп трейтов и статик ассерта?

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

из которой явно написано

из которой явно следует

anonymous
()

Хоспади, ну почему крестовиков постоянно тянет на эксперименты с сомнительным смыслом? Нахерачат, а потом краш дампами обмазываются.

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