Как правильно использовать:
static_cast
dynamic_cast
const_cast
reinterpret_cast
- С-стиль
(type)value
- Функциональный стиль
type(value)
Как решить, что использовать в каких конкретных случаях?
Как правильно использовать:
static_cast
dynamic_cast
const_cast
reinterpret_cast
(type)value
type(value)
Как решить, что использовать в каких конкретных случаях?
Ответы:
static_cast
это первый каст, который вы должны попытаться использовать. Он выполняет такие вещи, как неявные преобразования между типами (например, int
to float
или указатель на void*
), и может также вызывать функции явного преобразования (или неявные). Во многих случаях явное указание static_cast
не требуется, но важно отметить, что T(something)
синтаксис эквивалентен (T)something
и его следует избегать (подробнее об этом позже). Однако A T(something, something_else)
безопасен и гарантированно вызывает конструктор.
static_cast
может также приводиться через иерархии наследования. Это не нужно при приведении вверх (к базовому классу), но при приведении вниз его можно использовать до тех пор, пока оно не преобразуется через virtual
наследование. Однако он не выполняет проверку, и это неопределенное поведение - static_cast
приводить иерархию к типу, который на самом деле не является типом объекта.
const_cast
может использоваться для удаления или добавления const
к переменной; никакой другой C ++ cast не способен удалить его (даже не 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
это самый опасный состав, и должен использоваться очень экономно. Он превращает один тип непосредственно в другой - например, приведение значения от одного указателя к другому или сохранение указателя в 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
не удастся , Даже тогда рассмотрим более длинный и более явный вариант.
Приведения типа C также игнорируют управление доступом при выполнении a static_cast
, что означает, что они имеют возможность выполнять операции, которые не могут выполнять другие приведения. Хотя это в основном клудж, и, на мой взгляд, это просто еще одна причина избегать бросков в стиле C.
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);
.....
}
dynamic_cast:
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
как param. В качестве альтернативы, A
может иметь оператор преобразования (т.е. A::operator B()
). Если B
такого конструктора A
нет или у вас нет оператора преобразования, вы получите ошибку времени компиляции.A*
в B*
всегда выполняется успешно, если A и B находятся в иерархии наследования (или void), в противном случае вы получите ошибку компиляции.A&
к B&
.dynamic_cast
(Base*)
чтобы (Derived*)
может потерпеть неудачу , если указатель не на самом деле производного типа.A*
чтобы B*
, если бросок недействителен , то dynamic_cast вернет nullptr.A&
чтобы , B&
если литой недействителен , то dynamic_cast выбросит bad_cast исключения.const_cast
set<T>
который возвращает свои элементы только как const, чтобы убедиться, что вы не измените его ключ. Однако, если вы хотите изменить неключевые члены объекта, тогда все должно быть в порядке. Вы можете использовать const_cast для удаления константности.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, который может привести к segfault во время выполнения, если вам повезет. 2. Динамические броски могут также использоваться в перекрестном литье. 3. Const броски могут привести к 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
по одной причине. Я видел необработанные данные объекта, хранящиеся в типе данных «blob» в базе данных, а затем, когда данные извлекаются из базы данных, 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 заключается в том, что они скрывают реальные намерения разработчиков. Приведения в стиле C могут выполнять практически все типы приведения: от обычно безопасных приведения, выполняемых static_cast <> () и dynamic_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-стилями.
Вот небольшая цитата из книги Бьярна Страуструпа (автора C ++) «Язык программирования C ++, 4-е издание» - стр. 302.
Такое приведение в стиле C гораздо опаснее, чем именованные операторы преобразования, потому что обозначения труднее обнаружить в большой программе, а вид преобразования, предназначенный программистом, не является явным.
Чтобы понять, давайте рассмотрим ниже фрагмент кода:
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 может использоваться для преобразования указателя на объект в указатель на любой не связанный тип объекта.
Следует отметить следующее: dynamic_cast не будет работать во время выполнения, однако на большинстве компиляторов он также не будет компилироваться, поскольку в структуре преобразуемого указателя нет виртуальных функций, то есть dynamic_cast будет работать только с полиморфными указателями классов. ,
Когда использовать C ++ cast :
static_cast
по dynamic_cast
сравнению с reinterpret_cast
внутренним видом на downcast / upcast
В этом ответе я хочу сравнить эти три механизма на конкретном примере upcast / downcast и проанализировать, что происходит с базовыми указателями / памятью / сборкой, чтобы дать конкретное понимание того, как они сравниваются.
Я считаю, что это даст хорошую интуицию о том, как эти броски отличаются:
static_cast
: делает одно смещение адреса во время выполнения (низкое влияние времени выполнения) и не проверяет безопасность, что даунскейт корректен.
dyanamic_cast
: делает то же самое смещение адреса во время выполнения, как static_cast
, но также и дорогостоящую проверку безопасности, что обратное преобразование корректно, используя RTTI.
Эта проверка безопасности позволяет вам запрашивать, имеет ли указатель базового класса заданный тип во время выполнения, проверяя возвращение nullptr
которого указывает на недопустимое снижение.
Поэтому, если ваш код не может проверить это nullptr
и выполнить допустимое действие без прерывания, вы должны просто использовать static_cast
вместо динамического приведения.
Если прерывание - это единственное действие, которое может выполнить ваш код, возможно, вам нужно только включить dynamic_cast
in in debug builds ( -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
внутренне.
Поэтому мы приходим к критическому выводу:
upcast или downcast нужно только сместить значение указателя на значение, известное во время компиляции
Таким образом, когда D
передается в массив базового типа, приведение типа фактически вычисляет это смещение и указывает на то, что выглядит точно как действительное B2
в памяти:
b2s[1] = &d;
за исключением того, что у этого есть vtable 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, если значение einput равно NULL.
В противном случае он устанавливает некоторые аргументы в RDX, RSI и RDI и вызовах __dynamic_cast
.
У меня нет терпения, чтобы проанализировать это сейчас, но, как говорили другие, единственный способ для этого - __dynamic_cast
получить доступ к некоторым дополнительным структурам данных RTTI в памяти, которые представляют иерархию классов.
Поэтому он должен начинаться с B2
записи для этой таблицы, а затем обходить эту иерархию классов, пока не обнаружит, что vtable для типа D
приведен из b2s[0]
.
Вот почему реинтерпретация броска потенциально дорогая! Вот пример, где один патч, конвертирующий dynamic_cast
a static_cast
в сложный проект, сократил время выполнения на 33%! ,
reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940
этот просто верит нам слепо: мы сказали, что есть D
адрес at b2s[1]
, а компилятор не выполняет вычисления смещения.
Но это неправильно, потому что D на самом деле в 0x7fffffffc930, то, что в 0x7fffffffc940 - это B2-подобная структура внутри 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.