Как правильно использовать:
static_castdynamic_castconst_castreinterpret_cast- С-стиль
(type)value - Функциональный стиль
type(value)
Как решить, что использовать в каких конкретных случаях?
Как правильно использовать:
static_castdynamic_castconst_castreinterpret_cast(type)valuetype(value)Как решить, что использовать в каких конкретных случаях?
Ответы:
static_castэто первый каст, который вы должны попытаться использовать. Он выполняет такие вещи, как неявные преобразования между типами (например, intto 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_caststatic_cast (хотя игнорируя ограничения доступа)static_cast (см. выше), затем const_castreinterpret_castreinterpret_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_castin 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_casta 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.