Які належні способи використання:
static_cast
dynamic_cast
const_cast
reinterpret_cast
- Акторський склад у стилі C
(type)value
- Акторський склад
type(value)
Як можна вирішити, що використовувати в яких конкретних випадках?
Які належні способи використання:
static_cast
dynamic_cast
const_cast
reinterpret_cast
(type)value
type(value)
Як можна вирішити, що використовувати в яких конкретних випадках?
Відповіді:
static_cast
це перший склад, який ви повинні спробувати використати. Він робить такі речі, як неявні перетворення між типами (наприклад, int
до float
або вказівник на void*
), а також може викликати явні функції перетворення (або неявні). У багатьох випадках прямо заявляти static_cast
не потрібно, але важливо зазначити, що T(something)
синтаксис еквівалентний (T)something
і його слід уникати (докладніше про це пізніше). Однак T(something, something_else)
це безпечно і гарантовано дзвонить конструктору.
static_cast
може також містити ієрархії спадкування. Це є непотрібним при закиданні вгору (до базового класу), але при закиданні вниз його можна використовувати до тих пір, поки це не буде передано через virtual
спадщину. Однак це не робить перевірки, і це не визначене поведінка, щоб static_cast
знизити ієрархію до типу, який насправді не є типом об'єкта.
const_cast
може використовуватися для видалення або додавання const
змінної; жоден інший склад C ++ не може його видалити (навіть не reinterpret_cast
). Важливо зауважити, що зміна колишнього const
значення не визначається лише тоді, коли початкова змінна є const
; якщо ви використовуєте його, щоб зняти const
посилання на те, що не було оголошено const
, це безпечно. Це може бути корисно, наприклад, при перевантаженні функцій членів на основі const
. Він також може використовуватися для додавання const
до об'єкта, наприклад, для виклику перевантаження функції члена.
const_cast
також працює аналогічно volatile
, хоча це рідше.
dynamic_cast
використовується виключно для боротьби з поліморфізмом. Ви можете передавати покажчик або посилання на будь-який поліморфний тип до будь-якого іншого типу класу (поліморфний тип має принаймні одну віртуальну функцію, оголошену або успадковану). Ви можете використовувати його для більш ніж просто закидання вниз - ви можете кинути набік або навіть підняти інший ланцюжок. dynamic_cast
Буде шукати потрібний об'єкт і повернути його , якщо це можливо. Якщо він не зможе, він повернеться nullptr
у разі вказівника або викине std::bad_cast
у випадку посилання.
dynamic_cast
Однак є деякі обмеження. Це не працює, якщо в ієрархії спадкування є декілька об'єктів одного типу (так званий "страшний діамант") і ви не використовуєте virtual
спадкування. Вона також може йти тільки через публічне спадкування - це завжди буде не в подорож через protected
або private
успадкування. Однак це рідко є проблемою, оскільки такі форми успадкування є рідкісними.
reinterpret_cast
є найнебезпечнішим складом, і його слід використовувати дуже щадно. Він перетворює один тип безпосередньо в інший - наприклад, передачу значення з одного вказівника на інший, або зберігання вказівника у an int
або всілякі інші неприємні речі. В основному, єдиною гарантією, яку ви отримуєте, reinterpret_cast
є те, що зазвичай, якщо ви повернете результат до початкового типу, ви отримаєте точно таке ж значення (але не, якщо проміжний тип менший, ніж вихідний тип). Існує ряд конверсій, які reinterpret_cast
теж не можуть зробити. Він використовується в основному для особливо дивних перетворень і маніпуляцій бітом, як перетворення необробленого потоку даних у фактичні дані або зберігання даних у низьких бітах вказівника до вирівняних даних.
C-стиль лиття і функція стиль кидок є зліпки з використанням (type)object
або type(object)
, відповідно, і функціонально еквівалентні. Вони визначаються як перший із наступних, що вдається:
const_cast
static_cast
(хоча ігноруючи обмеження доступу)static_cast
(див. вище), потім const_cast
reinterpret_cast
reinterpret_cast
, тоді const_cast
Тому в деяких випадках він може використовуватися як заміна для інших ролей, але може бути вкрай небезпечним через здатність переходити в a reinterpret_cast
, і останнім слід віддати перевагу тоді, коли потрібне явне кастинг, якщо ви не впевнені static_cast
, що вдасться чи reinterpret_cast
не вдасться . Вже тоді розглянемо більш тривалий, чіткіший варіант.
Заготівки в стилі С також ігнорують контроль доступу під час виконання значка "" static_cast
, що означає, що вони мають можливість виконувати операцію, яку не може виконати жодна інша особа. Це, головним чином, хитрість, і, на мою думку, це лише ще одна причина, щоб уникнути кастингу в стилі С.
const
(навіть не reinterpret_cast
)" ... дійсно? Про що reinterpret_cast<int *>(reinterpret_cast<uintptr_t>(static_cast<int const *>(0)))
?
reinterpret_cast
часто це зброя вибору при роботі з набором непрозорих типів даних API
Використовувати dynamic_cast
для перетворення покажчиків / посилань у межах ієрархії спадкування.
Використовуйте static_cast
для перетворень звичайного типу.
Використовуйте reinterpret_cast
для низькорівневої переінтерпретації бітових шаблонів. Використовуйте з особливою обережністю.
Використовуйте const_cast
для відкидання const/volatile
. Уникайте цього, якщо ви не застрягли, використовуючи некоректно API.
(Вище було дано багато теоретичних та концептуальних пояснень)
Нижче наведені деякі з практичних прикладів , коли я використовував static_cast , dynamic_cast , const_cast , reinterpret_cast .
(Звертається також до цього, щоб зрозуміти пояснення: http://www.cplusplus.com/doc/tutorial/typecasting/ )
static_cast:
OnEventData(void* pData)
{
......
// pData is a void* pData,
// EventData is a structure e.g.
// typedef struct _EventData {
// std::string id;
// std:: string remote_id;
// } EventData;
// On Some Situation a void pointer *pData
// has been static_casted as
// EventData* pointer
EventData *evtdata = static_cast<EventData*>(pData);
.....
}
динамічний_каст:
void DebugLog::OnMessage(Message *msg)
{
static DebugMsgData *debug;
static XYZMsgData *xyz;
if(debug = dynamic_cast<DebugMsgData*>(msg->pdata)){
// debug message
}
else if(xyz = dynamic_cast<XYZMsgData*>(msg->pdata)){
// xyz message
}
else/* if( ... )*/{
// ...
}
}
const_cast:
// *Passwd declared as a const
const unsigned char *Passwd
// on some situation it require to remove its constness
const_cast<unsigned char*>(Passwd)
reinterpret_cast:
typedef unsigned short uint16;
// Read Bytes returns that 2 bytes got read.
bool ByteBuffer::ReadUInt16(uint16& val) {
return ReadBytes(reinterpret_cast<char*>(&val), 2);
}
static_cast<char*>(&val)
?
static_cast
працює лише між типами з визначеними перетвореннями, видимим відношенням за спадщиною або з / з void *
. Для всього іншого є інші касти. reinterpret cast
будь-якому char *
типу дозволено дозволяти читати представлення будь-якого об’єкта - і один з єдиних випадків, коли це ключове слово корисне, а не розгалужений генератор поведінки втілення / невизначеності. Але це не вважається "нормальним" перетворенням, тому не допускається (як правило) дуже консервативно static_cast
.
Це може допомогти, якщо ви знаєте трохи внутрішніх справ ...
static_cast
static_cast
для них.A
в B
, конструктор static_cast
викликів B
передає A
як парам. Крім того, A
може мати оператор перетворення (тобто A::operator B()
). Якщо B
такого конструктора A
немає або немає оператора перетворення, ви отримуєте помилку часу компіляції.A*
до B*
завжди успішна , якщо А і В знаходяться в ієрархії успадкування (або порожнечі) в іншому випадку ви отримаєте помилку компіляції.A&
до B&
.динамічна передача
(Base*)
вийти з (Derived*)
ладу, якщо покажчик насправді не має похідного типу.A*
для B*
, якщо cast недійсний, тоді динамичний_cast поверне nullptr.A&
що B&
якщо команда cast недійсна, динамічний_cast закидає виняток bad_cast.const_cast
set<T>
який повертає його елементи лише як const, щоб переконатися, що ви не змінюєте його ключ. Однак якщо ваш намір полягає в зміні не-ключових членів об'єкта, тоді це повинно бути нормально. Ви можете використовувати const_cast для видалення constness.T& SomeClass::foo()
, а також const T& SomeClass::foo() const
. Щоб уникнути дублювання коду, ви можете застосувати const_cast, щоб повернути значення однієї функції з іншої.reinterpret_cast
If you cast base pointer to derived pointer but if actual object is not really derived type then you don't get error. You get bad pointer and segfault at runtime.
Ви отримуєте UB, що може призвести до сегментації під час виконання, якщо вам пощастить. 2. Динамічні лиття також можуть використовуватися при перехресному литті. 3. Конст-фусти в деяких випадках можуть спричинити за собою UB. Використання mutable
може бути кращим вибором для реалізації логічної стійкості.
mutable
, перехресне кастинг тощо.
Чи відповідає це на ваше запитання?
Я ніколи не використовував reinterpret_cast
і цікавлюсь, чи натрапляти на справу, яка потребує цього, це не запах поганого дизайну. У кодовій базі, над якою я працюю dynamic_cast
, використовується багато. Різниця в static_cast
тому, що dynamic_cast
перевірка часу виконання, яка може (безпечніше) або не (більше накладних витрат) буде тим, що ви хочете (див. Msdn ).
reinterpret_cast
для витягування фрагментів даних з масиву. Наприклад, якщо у мене є char*
великий буфер, повний упакованих бінарних даних, які мені потрібно перенести і отримати окремі примітиви різних типів. Приблизно так:template<class ValType> unsigned int readValFromAddress(char* addr, ValType& val) { /*On platforms other than x86(_64) this could do unaligned reads, which could be bad*/ val = (*(reinterpret_cast<ValType*>(addr))); return sizeof(ValType); }
reinterpret_cast
, для цього не дуже багато застосувань.
reinterpret_cast
використаний з однієї причини. Я бачив необроблені дані об'єкта, що зберігаються в "базі" типу даних у базі даних, тоді, коли дані отримані з бази даних, reinterpret_cast
використовуються для перетворення цих необроблених даних у об'єкт.
Окрім інших поки що відповідей, ось очевидний приклад, коли static_cast
цього недостатньо, щоб це reinterpret_cast
було потрібно. Припустимо, є функція, яка у вихідному параметрі повертає покажчики на об’єкти різних класів (які не мають загального базового класу). Справжнім прикладом такої функції є CoCreateInstance()
(див. Останній параметр, який є насправді void**
). Припустимо, ви вимагаєте від цієї функції певного класу об'єктів, тому ви заздалегідь знаєте тип для вказівника (що ви часто робите для об’єктів COM). У цьому випадку ви не можете кинути вказівник на ваш покажчик за void**
допомогою static_cast
: вам потрібно reinterpret_cast<void**>(&yourPointer)
.
У коді:
#include <windows.h>
#include <netfw.h>
.....
INetFwPolicy2* pNetFwPolicy2 = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr,
CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),
//static_cast<void**>(&pNetFwPolicy2) would give a compile error
reinterpret_cast<void**>(&pNetFwPolicy2) );
Однак static_cast
працює для простих покажчиків (не покажчиків на покажчики), тому вищевказаний код можна переписати, щоб уникнути reinterpret_cast
(за ціною додаткової змінної) наступним чином:
#include <windows.h>
#include <netfw.h>
.....
INetFwPolicy2* pNetFwPolicy2 = nullptr;
void* tmp = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr,
CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),
&tmp );
pNetFwPolicy2 = static_cast<INetFwPolicy2*>(tmp);
&static_cast<void*>(pNetFwPolicy2)
замість static_cast<void**>(&pNetFwPolicy2)
?
Хоча в інших відповідях добре описані всі відмінності між C ++ кастрами, я хотів би додати коротке зауваження, чому ви не повинні використовувати касти в стилі C (Type) var
і Type(var)
.
Для початківців C ++ касти в стилі C виглядають як операція суперсети над C ++ кастрами (static_cast <> (), dynamic_cast <> (), const_cast <> (), reinterpret_cast <> ()), і хтось міг би віддавати перевагу їм над кастрами C ++ . Насправді акторський склад у стилі С - це суперсеть і коротше писати.
Основна проблема кастингу в стилі C полягає в тому, що вони приховують в розробника реальні наміри акторів. Заготівки в стилі C можуть виконувати практично всі типи кастингу від звичайно безпечних ролях, виконаних static_cast <> () та dinamiні_cast <> (), до потенційно небезпечних ролей, таких як const_cast <> (), де модифікатор const можна видалити, щоб змінні const може бути змінено та reinterpret_cast <> (), що навіть може повторно інтерпретувати цілі значення в покажчики.
Ось зразок.
int a=rand(); // Random number.
int* pa1=reinterpret_cast<int*>(a); // OK. Here developer clearly expressed he wanted to do this potentially dangerous operation.
int* pa2=static_cast<int*>(a); // Compiler error.
int* pa3=dynamic_cast<int*>(a); // Compiler error.
int* pa4=(int*) a; // OK. C-style cast can do such cast. The question is if it was intentional or developer just did some typo.
*pa4=5; // Program crashes.
Основна причина, чому C ++ касти додавалися до мови, полягала в тому, щоб дозволити розробнику уточнити свої наміри - чому він збирається робити цю ролю. Використовуючи касти в стилі C, які є абсолютно дійсними для C ++, ви робите свій код менш читабельним і більше схильний до помилок, особливо для інших розробників, які не створили ваш код. Отже, щоб зробити ваш код більш читабельним і явним, завжди слід віддавати перевагу C ++ ролях над кастами в стилі C.
Ось коротка цитата з книги Bjarne Stroustrup (автора програми C ++) 4-е видання Мова програмування C ++ - стор. 302.
Цей склад у стилі С набагато небезпечніший, ніж названі оператори перетворення, оскільки позначення важче помітити у великій програмі, а тип перетворення, призначений програмістом, не є явним.
Щоб зрозуміти, розглянемо нижче фрагмент коду:
struct Foo{};
struct Bar{};
int main(int argc, char** argv)
{
Foo* f = new Foo;
Bar* b1 = f; // (1)
Bar* b2 = static_cast<Bar*>(f); // (2)
Bar* b3 = dynamic_cast<Bar*>(f); // (3)
Bar* b4 = reinterpret_cast<Bar*>(f); // (4)
Bar* b5 = const_cast<Bar*>(f); // (5)
return 0;
}
Тільки рядок (4) збирається без помилок. Лише reinterpret_cast може використовуватися для перетворення вказівника на об'єкт до вказівника до будь-якого непов'язаного типу об'єкта.
Одне, що слід зазначити, - це те, що в динамичному режимі не вдасться виконати час роботи, однак у більшості компіляторів він також не зможе компілювати, оскільки в структурі покажчика, що відкидається, немає віртуальних функцій, тобто динамичний_каст працюватиме лише з поліморфними вказівниками класу .
Коли використовувати C ++ ролик :
static_cast
vs dynamic_cast
проти reinterpret_cast
внутрішніх поглядів на низхідний / понівечений
У цій відповіді я хочу порівняти ці три механізми на конкретному прикладі оновлення / знищення та проаналізувати, що відбувається з основними вказівниками / пам'яттю / складанням, щоб дати конкретне розуміння того, як вони порівнюються.
Я вважаю, що це дасть добру інтуїцію щодо того, чим відрізняються ці ролі:
static_cast
: чи зміщується одна адреса під час виконання (низький вплив часу виконання) і чи не перевіряє безпеку правильність спаду.
dyanamic_cast
: чи однакове зміщення адреси під час виконання, як і static_cast
, але також і дорога безпека перевіряє правильність зниження інформації за допомогою RTTI.
Ця перевірка безпеки дозволяє запитувати, чи вказівник базового класу має заданий тип під час виконання, перевіряючи повернення nullptr
якого вказує на недійсний понижений рівень.
Тому, якщо ваш код не в змозі перевірити це nullptr
та здійснити дійсні дії, які не переривають, вам слід просто використовувати static_cast
замість динамічного амплітуди.
Якщо переривання - це єдине дію, яке може здійснити ваш код, можливо, ви хочете лише ввімкнути dynamic_cast
вбудовані помилки ( -NDEBUG
) і використовувати static_cast
інше, наприклад, як це зроблено тут , щоб не уповільнити швидкий пробіг.
reinterpret_cast
: не робить нічого під час виконання, навіть зміщення адреси. Вказівник повинен точно вказувати на правильний тип, навіть базовий клас не працює. Ви, як правило, цього не бажаєте, якщо не будуть залучені необмежені потоки байтів.
Розглянемо наступний приклад коду:
main.cpp
#include <iostream>
struct B1 {
B1(int int_in_b1) : int_in_b1(int_in_b1) {}
virtual ~B1() {}
void f0() {}
virtual int f1() { return 1; }
int int_in_b1;
};
struct B2 {
B2(int int_in_b2) : int_in_b2(int_in_b2) {}
virtual ~B2() {}
virtual int f2() { return 2; }
int int_in_b2;
};
struct D : public B1, public B2 {
D(int int_in_b1, int int_in_b2, int int_in_d)
: B1(int_in_b1), B2(int_in_b2), int_in_d(int_in_d) {}
void d() {}
int f2() { return 3; }
int int_in_d;
};
int main() {
B2 *b2s[2];
B2 b2{11};
D *dp;
D d{1, 2, 3};
// The memory layout must support the virtual method call use case.
b2s[0] = &b2;
// An upcast is an implicit static_cast<>().
b2s[1] = &d;
std::cout << "&d " << &d << std::endl;
std::cout << "b2s[0] " << b2s[0] << std::endl;
std::cout << "b2s[1] " << b2s[1] << std::endl;
std::cout << "b2s[0]->f2() " << b2s[0]->f2() << std::endl;
std::cout << "b2s[1]->f2() " << b2s[1]->f2() << std::endl;
// Now for some downcasts.
// Cannot be done implicitly
// error: invalid conversion from ‘B2*’ to ‘D*’ [-fpermissive]
// dp = (b2s[0]);
// Undefined behaviour to an unrelated memory address because this is a B2, not D.
dp = static_cast<D*>(b2s[0]);
std::cout << "static_cast<D*>(b2s[0]) " << dp << std::endl;
std::cout << "static_cast<D*>(b2s[0])->int_in_d " << dp->int_in_d << std::endl;
// OK
dp = static_cast<D*>(b2s[1]);
std::cout << "static_cast<D*>(b2s[1]) " << dp << std::endl;
std::cout << "static_cast<D*>(b2s[1])->int_in_d " << dp->int_in_d << std::endl;
// Segfault because dp is nullptr.
dp = dynamic_cast<D*>(b2s[0]);
std::cout << "dynamic_cast<D*>(b2s[0]) " << dp << std::endl;
//std::cout << "dynamic_cast<D*>(b2s[0])->int_in_d " << dp->int_in_d << std::endl;
// OK
dp = dynamic_cast<D*>(b2s[1]);
std::cout << "dynamic_cast<D*>(b2s[1]) " << dp << std::endl;
std::cout << "dynamic_cast<D*>(b2s[1])->int_in_d " << dp->int_in_d << std::endl;
// Undefined behaviour to an unrelated memory address because this
// did not calculate the offset to get from B2* to D*.
dp = reinterpret_cast<D*>(b2s[1]);
std::cout << "reinterpret_cast<D*>(b2s[1]) " << dp << std::endl;
std::cout << "reinterpret_cast<D*>(b2s[1])->int_in_d " << dp->int_in_d << std::endl;
}
Компілюйте, запустіть і розберіть:
g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
setarch `uname -m` -R ./main.out
gdb -batch -ex "disassemble/rs main" main.out
де setarch
це використовується , щоб відключити ASLR , щоб полегшити порівняння прогонів.
Можливий вихід:
&d 0x7fffffffc930
b2s[0] 0x7fffffffc920
b2s[1] 0x7fffffffc940
b2s[0]->f2() 2
b2s[1]->f2() 3
static_cast<D*>(b2s[0]) 0x7fffffffc910
static_cast<D*>(b2s[0])->int_in_d 1
static_cast<D*>(b2s[1]) 0x7fffffffc930
static_cast<D*>(b2s[1])->int_in_d 3
dynamic_cast<D*>(b2s[0]) 0
dynamic_cast<D*>(b2s[1]) 0x7fffffffc930
dynamic_cast<D*>(b2s[1])->int_in_d 3
reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940
reinterpret_cast<D*>(b2s[1])->int_in_d 32767
Тепер, як згадувалося на веб- сайті: https://en.wikipedia.org/wiki/Virtual_method_table , щоб ефективно підтримувати виклики віртуального методу, структура даних пам'яті D
має виглядати приблизно так:
B1:
+0: pointer to virtual method table of B1
+4: value of int_in_b1
B2:
+0: pointer to virtual method table of B2
+4: value of int_in_b2
D:
+0: pointer to virtual method table of D (for B1)
+4: value of int_in_b1
+8: pointer to virtual method table of D (for B2)
+12: value of int_in_b2
+16: value of int_in_d
Ключ в тому, що структура даних пам'яті D
містить всередині нього структуру пам'яті сумісна з B1
і з B2
внутрішньо.
Тому ми дійшли критичного висновку:
оновленому або похилому стану потрібно лише змістити значення вказівника на значення, відоме під час компіляції
Таким чином, коли D
передається до масиву базового типу, тип передачі фактично обчислює це зміщення і вказує щось, що виглядає точно як дійсне B2
в пам'яті:
b2s[1] = &d;
за винятком того, що один має для віртуальні таблиці D
замість B2
, і тому все віртуальні виклики працюють прозоро.
Тепер ми можемо нарешті повернутися до типу кастингу та аналізу нашого конкретного прикладу.
З виводу stdout ми бачимо:
&d 0x7fffffffc930
b2s[1] 0x7fffffffc940
Отже, static_cast
зроблені там неявні D
помилки правильно обчислили зміщення від повної структури даних на 0x7fffffffc930 до B2
подібної, яка знаходиться на 0x7fffffffc940. Ми також робимо висновок, що те, що лежить між 0x7fffffffc930 та 0x7fffffffc940, ймовірно, є B1
даними та vtable.
Тоді в розділах, що перебувають у спадному стані, тепер легко зрозуміти, як недійсні невдачі і чому:
static_cast<D*>(b2s[0]) 0x7fffffffc910
: компілятор просто збільшився на 0x10 під час компіляції, щоб спробувати перейти від a B2
до вмістуD
Але оскільки b2s[0]
це не було D
, це вказує на невизначений регіон пам'яті.
Розбирання:
49 dp = static_cast<D*>(b2s[0]);
0x0000000000000fc8 <+414>: 48 8b 45 d0 mov -0x30(%rbp),%rax
0x0000000000000fcc <+418>: 48 85 c0 test %rax,%rax
0x0000000000000fcf <+421>: 74 0a je 0xfdb <main()+433>
0x0000000000000fd1 <+423>: 48 8b 45 d0 mov -0x30(%rbp),%rax
0x0000000000000fd5 <+427>: 48 83 e8 10 sub $0x10,%rax
0x0000000000000fd9 <+431>: eb 05 jmp 0xfe0 <main()+438>
0x0000000000000fdb <+433>: b8 00 00 00 00 mov $0x0,%eax
0x0000000000000fe0 <+438>: 48 89 45 98 mov %rax,-0x68(%rbp)
тому ми бачимо, що GCC робить:
D
якого не існуєdynamic_cast<D*>(b2s[0]) 0
: C ++ фактично виявив, що акторський склад недійсний і повернувся nullptr
!
Це неможливо зробити під час компіляції, і ми підтвердимо це з розбирання:
59 dp = dynamic_cast<D*>(b2s[0]);
0x00000000000010ec <+706>: 48 8b 45 d0 mov -0x30(%rbp),%rax
0x00000000000010f0 <+710>: 48 85 c0 test %rax,%rax
0x00000000000010f3 <+713>: 74 1d je 0x1112 <main()+744>
0x00000000000010f5 <+715>: b9 10 00 00 00 mov $0x10,%ecx
0x00000000000010fa <+720>: 48 8d 15 f7 0b 20 00 lea 0x200bf7(%rip),%rdx # 0x201cf8 <_ZTI1D>
0x0000000000001101 <+727>: 48 8d 35 28 0c 20 00 lea 0x200c28(%rip),%rsi # 0x201d30 <_ZTI2B2>
0x0000000000001108 <+734>: 48 89 c7 mov %rax,%rdi
0x000000000000110b <+737>: e8 c0 fb ff ff callq 0xcd0 <__dynamic_cast@plt>
0x0000000000001110 <+742>: eb 05 jmp 0x1117 <main()+749>
0x0000000000001112 <+744>: b8 00 00 00 00 mov $0x0,%eax
0x0000000000001117 <+749>: 48 89 45 98 mov %rax,-0x68(%rbp)
Спочатку відбувається перевірка NULL, і вона повертає NULL, якщо e-вхід NULL.
В іншому випадку він встановлює деякі аргументи в RDX, RSI та RDI та викликах __dynamic_cast
.
Зараз у мене немає терпіння аналізувати це далі, але, як говорили інші, єдиний спосіб для цього - це __dynamic_cast
отримати доступ до деяких додаткових структур даних пам'яті RTTI, які представляють ієрархію класів.
Тому він повинен починати з B2
запису для цієї таблиці, а потім ходити по цій ієрархії класів, поки не виявить, що vtable для D
typecast від b2s[0]
.
Ось чому переосмислена ролях потенційно дорога! Ось приклад, коли патч одного вкладиша, який перетворює dynamic_cast
a static_cast
в складний проект, скоротив час виконання на 33%! .
reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940
цей нам просто сліпо вірить: ми сказали, що є D
адреса b2s[1]
, і компілятор не робить компенсації зсуву.
Але це неправильно, тому що D насправді знаходиться на 0x7fffffffc930, що в 0x7fffffffc940 - це структура, подібна до В2 всередині D! Тож доступ до сміття отримує доступ.
Ми можемо підтвердити це з жахливої -O0
збірки, яка просто переміщує значення навколо:
70 dp = reinterpret_cast<D*>(b2s[1]);
0x00000000000011fa <+976>: 48 8b 45 d8 mov -0x28(%rbp),%rax
0x00000000000011fe <+980>: 48 89 45 98 mov %rax,-0x68(%rbp)
Пов’язані запитання:
Тестовано на Ubuntu 18.04 amd64, GCC 7.4.0.