Aşağıdakilerin uygun kullanımları nelerdir:
static_cast
dynamic_cast
const_cast
reinterpret_cast
- C tarzı döküm
(type)value
- İşlev tarzı döküm
type(value)
Hangi özel durumlarda hangisinin kullanılacağına nasıl karar verilir?
Aşağıdakilerin uygun kullanımları nelerdir:
static_cast
dynamic_cast
const_cast
reinterpret_cast
(type)value
type(value)
Hangi özel durumlarda hangisinin kullanılacağına nasıl karar verilir?
Yanıtlar:
static_cast
kullanmaya çalışmanız gereken ilk oyuncu. Bu örtük (örneğin türleri arasındaki dönüşümler gibi şeyler yapar int
için float
veya işaretçi void*
) ve aynı zamanda açık dönüştürme işlevleri (veya örtülü olanlar) çağırabilir. Çoğu durumda, açıkça belirtmek static_cast
gerekli değildir, ancak T(something)
sözdiziminin eşdeğer olduğunu (T)something
ve bundan kaçınılması gerektiğini belirtmek önemlidir (daha sonra daha fazla). T(something, something_else)
Ancak A güvenlidir ve kurucuyu çağırması garanti edilir.
static_cast
kalıtım hiyerarşileri yoluyla da kullanılabilir. Yukarı doğru (bir temel sınıfa doğru) döküm yaparken gereksizdir, ancak aşağı doğru döküm yaparken, virtual
kalıtımdan geçmediği sürece kullanılabilir . Ancak denetleme yapmaz ve static_cast
bir hiyerarşiyi aslında nesnenin türü olmayan bir türe indirgemek tanımsız bir davranıştır .
const_cast
const
bir değişkeni kaldırmak veya eklemek için kullanılabilir ; başka hiçbir C ++ dökümü onu kaldıramaz (hatta değil reinterpret_cast
). Önceden bir const
değerin değiştirilmesinin yalnızca orijinal değişken ise tanımsız olduğunu belirtmek önemlidir const
; const
bildirilmemiş bir şeye referans almak için kullanırsanız const
, güvenlidir. Bu, örneğin üye fonksiyonlarını aşırı yüklerken faydalı olabilir const
. const
Bir üye fonksiyonunun aşırı yüklenmesi gibi bir nesneye eklemek için de kullanılabilir .
const_cast
volatile
daha az yaygın olsa da benzer şekilde çalışır .
dynamic_cast
sadece polimorfizmi işlemek için kullanılır. Bir işaretçiyi veya herhangi bir polimorfik türe referansı başka herhangi bir sınıf türüne atayabilirsiniz (polimorfik bir türün bildirilen veya devralınan en az bir sanal işlevi vardır). Sadece aşağı doğru dökmekten daha fazlası için kullanabilirsiniz - yanlara veya hatta başka bir zincire kadar dökebilirsiniz. dynamic_cast
İstenen nesneyi ararlar ve mümkünse bunu dönecektir. Eğer yapamazsa, nullptr
bir işaretçi durumunda geri dönecek ya da std::bad_cast
başvuru durumunda atılacaktır .
dynamic_cast
olsa da, bazı sınırlamaları vardır. Kalıtım hiyerarşisinde ('korkunç elmas' olarak adlandırılan) aynı türde birden fazla nesne varsa ve miras kullanmıyorsanız işe yaramaz virtual
. Ayrıca sadece kamusal mirastan geçebilir - her zaman seyahat etmek protected
veya private
miras almak başarısız olur . Bununla birlikte, bu tür bir kalıtım biçimi nadir olduğu için bu nadiren bir konudur.
reinterpret_cast
en tehlikeli dökümdür ve çok az kullanılmalıdır. Bir türü doğrudan diğerine dönüştürür - örneğin değeri bir işaretçiden diğerine çevirmek veya bir işaretçiyi bir int
veya başka türlü kötü şeylerde saklamak gibi . Büyük ölçüde, sadece birlikte olsun garanti reinterpret_cast
orijinal türüne sonucu geri dökme normalde bile, tam olarak aynı değere alacak (ancak değil orta tip orijinal tip küçükse). Yapamayan birkaç dönüşüm reinterpret_cast
de var. Öncelikle, ham veri akışını gerçek verilere dönüştürmek veya hizalanmış verilere işaretçinin düşük bitlerinde veri depolamak gibi özellikle garip dönüşümler ve bit manipülasyonları için kullanılır.
C stili döküm ve işlev stili döküm , sırasıyla (type)object
veya kullanan dökümlerdir type(object)
ve işlevsel olarak eşdeğerdir. Bunlar, aşağıdakilerden başarılı olan aşağıdakilerden birincisi olarak tanımlanır:
const_cast
static_cast
(erişim kısıtlamalarına uymamakla birlikte)static_cast
(yukarıya bakın), sonra const_cast
reinterpret_cast
reinterpret_cast
, sonra const_cast
Bu nedenle, bazı durumlarda diğer dökümlerin yerine kullanılabilir, ancak a'ya dönüşme yeteneği nedeniyle son derece tehlikeli olabilir reinterpret_cast
ve ikincisi, açık döküm gerektiğinde, static_cast
başarılı reinterpret_cast
olacağınızdan veya başarısız olacağınızdan emin olmadan tercih edilmelidir. . O zaman bile, daha uzun, daha açık seçeneği düşünün.
C tarzı dökümler static_cast
, a yaparken erişim kontrolünü de yok sayar , bu da başka hiçbir dökümün yapamayacağı bir işlem gerçekleştirme yeteneğine sahip oldukları anlamına gelir. Bu çoğunlukla bir çamurdur ve bence C tarzı dökümlerden kaçınmanın başka bir nedeni.
const
(hatta değil reinterpret_cast
) kaldıramaz " ... gerçekten? Ne olmuş reinterpret_cast<int *>(reinterpret_cast<uintptr_t>(static_cast<int const *>(0)))
?
reinterpret_cast
Bir API'nin opak veri türleri kümesi ile uğraşırken bunun genellikle tercih edilen silah olduğunu belirtmek gerekebilir
dynamic_cast
Kalıtım hiyerarşisinde işaretçileri / referansları dönüştürmek için kullanın .
static_cast
Normal tür dönüşümler için kullanın .
reinterpret_cast
Bit örüntülerinin düşük seviye yeniden yorumlanması için kullanın . Çok dikkatli kullanın.
const_cast
Döküm yapmak için kullanın const/volatile
. Bir const-false API kullanarak sıkışıp kalmadıkça bunu önlemek.
(Yukarıda birçok teorik ve kavramsal açıklama verilmiştir)
Aşağıda static_cast , dynamic_cast , const_cast , reinterpret_cast kullandığım bazı pratik örnekler var .
(Ayrıca açıklamayı anlamak için bunu ifade eder: 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)
mi?
static_cast
yalnızca tanımlanmış dönüşümleri olan türler arasında, kalıtım yoluyla veya / ile görünür ilişki arasında çalışır void *
. Diğer her şey için başka oyuncular var. reinterpret cast
herhangi bir char *
türün herhangi bir nesnenin temsilinin okunmasına izin vermesine izin verilir - ve bu anahtar kelimenin yararlı olduğu tek durumlardan biri, uygulama / tanımsız davranışların yaygın bir üreticisi değil. Ancak bu 'normal' bir dönüşüm olarak kabul edilmez, bu nedenle (genellikle) çok muhafazakarlar tarafından izin verilmez static_cast
.
Biraz içsel olanı biliyorsanız yardımcı olabilir ...
static_cast
static_cast
Onlar için kullanın .A
istediğinizde B
, static_cast
çağrının B
yapıcısını A
param olarak iletir. Alternatif olarak, A
bir dönüştürme operatörüne (yani A::operator B()
) sahip olabilir. Eğer B
böyle yapıcı yok, ya A
bir dönüşüm operatörü yoktur, o zaman derleme zamanı hatası alırsınız.A*
için B*
A ve B kalıtım hiyerarşisi (veya boşluk) ise her zaman aksi takdirde derleme hatası alıyorum başarır.A&
de geçerli B&
.dynamic_cast
(Base*)
için (Derived*)
işaretçi aslında türetilmiş bir tür değilse başarısız olabilir.A*
için B*
döküm ise, geçersiz sonra dynamic_cast nullptr dönecektir.A&
için B&
dökme geçersizse sonra dynamic_cast bad_cast özel durum oluşturur.const_cast
set<T>
, anahtarını değiştirmediğinizden emin olmak için öğelerini yalnızca const olarak döndüren bir kapsayıcıdan yinelenmektir . Ancak amacınız nesnenin anahtar olmayan üyelerini değiştirmekse, o zaman sorun olmamalıdır. Sabitliği kaldırmak için const_cast kullanabilirsiniz.T& SomeClass::foo()
yanı sıra const T& SomeClass::foo() const
. Kod çoğaltmasını önlemek için, bir işlevin değerini diğerinden döndürmek için const_cast uygulayabilirsiniz.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.
Eğer şanslıysanız çalışma zamanında bir segfault neden olabilir UB olsun. 2. Dinamik dökümler çapraz dökümde de kullanılabilir. 3. Sabit oyuncular bazı durumlarda UB ile sonuçlanabilir. Kullanımı mutable
mantıksal sabitliği uygulamak için daha iyi bir seçim olabilir.
mutable
, çapraz döküm
Does bu sorunuza cevap?
Hiç kullanmadım reinterpret_cast
ve ihtiyaç duyan bir davaya girmenin kötü bir tasarım kokusu olmadığını merak ediyorum. Üzerinde çalıştığım kod tabanında dynamic_cast
çok kullanılıyor. Aradaki fark static_cast
, istediğiniz dynamic_cast
(daha güvenli) veya daha fazla (daha fazla yük) olabilecek bir çalışma zamanı denetiminin yapılmasıdır ( msdn'ye bakın ).
reinterpret_cast
bir dizi veri parçaları ayıklamak için kullanın . Örneğin char*
, hareket etmem ve farklı türlerde bireysel ilkelleri almam gereken paketlenmiş ikili verilerle dolu büyük bir arabelleğim varsa . Böyle bir şey: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
, bunun için çok fazla kullanım yok.
reinterpret_cast
bir sebepten dolayı kullanıldığını gördüm . Ben bir veritabanında bir "blob" veri tipinde saklanan ham nesne verileri gördüm, sonra veri veritabanından alındığında, reinterpret_cast
bu ham verileri nesneye dönüştürmek için kullanılır.
Şimdiye kadar olan diğer cevaplara ek olarak, burada static_cast
yeterli olmadığı reinterpret_cast
ve ihtiyaç duyulmadığı açık olmayan bir örnek . Bir çıkış parametresinde farklı sınıftaki nesnelere (ortak bir temel sınıfı paylaşmayan) işaretçiler döndüren bir işlev olduğunu varsayalım. Bu işlevin gerçek bir örneği ( CoCreateInstance()
aslında son parametreye bakın void**
). Bu işlevden belirli bir nesne sınıfı istediğinizi varsayalım, böylece işaretçinin türünü önceden bilirsiniz (genellikle COM nesneleri için yaparsınız). Bu durumda işaretçiyi işaretçinize void**
şununla atamazsınız static_cast
: ihtiyacınız var reinterpret_cast<void**>(&yourPointer)
.
Kodda:
#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) );
Bununla birlikte, static_cast
basit işaretçiler (işaretleyicilere işaretçiler için değil) için çalışır, bu nedenle yukarıdaki kod reinterpret_cast
(ekstra değişkenin bir fiyatında) aşağıdaki şekilde önlemek için yeniden yazılabilir :
#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)
Bunun yerine böyle bir şey olmaz static_cast<void**>(&pNetFwPolicy2)
mı?
Diğer cevaplar güzel C ++ atmalarını arasındaki tüm farklılıkları tarif ederken, ben C tarzı yayınları kullanmamalısınız neden kısa bir not eklemek istiyorum (Type) var
ve Type(var)
.
C ++ yeni başlayanlar için C tarzı dökümler, C ++ yayınları (static_cast <> (), dynamic_cast <> (), const_cast <> (), reinterpret_cast <> ()) üzerinde üst küme işlemi gibi görünür ve birisi bunları C ++ yayınları üzerinde tercih edebilir . Aslında C tarzı döküm süper set ve yazmak için daha kısa.
C tarzı dökümlerin temel sorunu geliştiricinin oyuncu kadrosunun gerçek niyetini gizlemesidir. C stili dökümler, static_cast <> () ve dynamic_cast <> () tarafından yapılan normalde güvenli dökümlerden const değişkeninin kaldırılabileceği const_cast <> () gibi potansiyel olarak tehlikeli dökümlere kadar neredeyse tüm döküm türlerini yapabilir değiştirilebilir ve tamsayı değerlerini işaretçilerle yeniden yorumlayabilen reinterpret_cast <> ().
İşte örnek.
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 ++ dillerinin dile eklenmesinin ana nedeni, bir geliştiricinin niyetlerini netleştirmesine izin vermekti - neden bu kadroyu yapacak. C ++ 'da mükemmel şekilde geçerli olan C tarzı dökümler kullanarak, kodunuzu daha az okunabilir hale getirir ve özellikle kodunuzu oluşturmayan diğer geliştiriciler için daha fazla hataya açık hale gelirsiniz. Bu nedenle, kodunuzu daha okunabilir ve açık hale getirmek için her zaman C stili yayınlar yerine C ++ yayınlarını tercih etmelisiniz.
İşte Bjarne Stroustrup'un (C ++ yazarı) kitabı C ++ Programlama Dili 4. baskıdan kısa bir alıntı - sayfa 302.
Bu C tarzı döküm, adlandırılan dönüşüm operatörlerinden çok daha tehlikelidir, çünkü gösterimi büyük bir programda tespit etmek daha zordur ve programcı tarafından amaçlanan dönüşüm türü açık değildir.
Anlamak için aşağıdaki kod snippet'ini ele alalım:
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;
}
Yalnızca satır (4) hatasız derlenir. Yalnızca reinterpret_cast , bir işaretçiyi bir nesneye işaretçiyle ilgisiz herhangi bir nesne türüne dönüştürmek için kullanılabilir.
Dikkat edilmesi gereken noktalardan biri şudur: dynamic_cast çalışma zamanında başarısız olur, ancak çoğu derleyicide de derleme başarısız olur çünkü imlecin yapısında sanal işlevler yoktur, yani dynamic_cast sadece polimorfik sınıf işaretçileriyle çalışır .
C ++ cast ne zaman kullanılır :
static_cast
vs dynamic_cast
vs reinterpret_cast
internals downcast / upcast görünüm
Bu cevapta, somut bir inişli çıkışlı / inişli çıkış örneğinde bu üç mekanizmayı karşılaştırmak ve nasıl karşılaştırıldığına dair somut bir anlayış sağlamak için altta yatan işaretlere / bellek / düzeneğe ne olduğunu analiz etmek istiyorum.
Bunun, bu yayınların nasıl farklı olduğu konusunda iyi bir sezgi vereceğine inanıyorum:
static_cast
: çalışma zamanında bir adres ofseti yapar (düşük çalışma zamanı etkisi) ve bir downcast'in doğru olduğunu kontrol etmez.
dyanamic_cast
: çalışma zamanında aynı adres ofsetini yapar, aynı static_cast
zamanda ve RTTI kullanarak bir downcast'in doğru olup olmadığını pahalı bir güvenlik kontrolü yapar.
Bu güvenlik denetimi, bir temel sınıf işaretçisinin çalışma zamanında belirli bir türde olup nullptr
olmadığını, geri dönüşünü geçersiz bir downcast işaret ettiğini kontrol ederek sorgulamanızı sağlar .
Bu nedenle, kodunuz bunu kontrol edemiyor nullptr
ve geçerli bir iptal edilmedi eylemi gerçekleştiremiyorsa, yalnızca static_cast
dinamik döküm yerine kullanmanız gerekir .
Bir iptal işlemi kodunuzun gerçekleştirebileceği tek işlemse, belki de yalnızca dynamic_cast
hata ayıklama yapılarını ( -NDEBUG
) etkinleştirmek ve hızlı çalışmalarınızı yavaşlatmamak için static_cast
örneğin burada yapıldığı gibi başka şekilde kullanmak istersiniz .
reinterpret_cast
: çalışma zamanında hiçbir şey yapmaz, adres ofseti bile. İşaretçi, temel sınıfın bile çalışmadığından tam olarak doğru türün üzerine gelmelidir. Ham bayt akışları sürece genellikle bunu istemezsiniz.
Aşağıdaki kod örneğini düşünün:
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;
}
Aşağıdakilerle derleyin, çalıştırın ve sökün:
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
nerede çalıştırmayı karşılaştırmayı kolaylaştırmak için ASLR'yi devre dışı bırakmaksetarch
için kullanılır .
Olası çıktı:
&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
Şimdi, sanal yöntem çağrılarını verimli bir şekilde desteklemek için https://en.wikipedia.org/wiki/Virtual_method_table adresinde belirtildiği gibi , bellek veri yapısı aşağıdaki D
gibi görünmelidir:
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
Anahtar gerçek şu ki, içerisindeki bellek veri yapısı D
, içerideki B1
ve B2
dahili olanla uyumlu olan bellek yapısını içerir .
Bu nedenle kritik sonuca ulaşıyoruz:
upcast veya downcast yalnızca işaretçi değerini derleme zamanında bilinen bir değere kaydırmalıdır.
Bu şekilde, D
temel tür dizisine geçtiğinde, cast türü aslında bu dengeyi hesaplar ve tam olarak B2
bellekte geçerli gibi görünen bir şeyi işaret eder :
b2s[1] = &d;
D
bunun yerine bunun için vtable olması B2
ve bu nedenle tüm sanal çağrılar şeffaf bir şekilde çalışır.
Şimdi nihayet tip dökümüne ve somut örneğimizin analizine geri dönebiliriz.
Stdout çıktısından şunu görüyoruz:
&d 0x7fffffffc930
b2s[1] 0x7fffffffc940
Bu nedenle, static_cast
orada yapılan örtük D
, 0x7fffffffc930'daki tam veri yapısından 0x7fffffffc940'takine B2
benzer bir şekilde ofseti doğru bir şekilde hesapladı . Ayrıca, 0x7fffffffc930 ve 0x7fffffffc940 arasında yatan şeyin muhtemelen B1
veri ve vtable olduğunu da çıkarıyoruz.
Sonra, downcast bölümlerinde, geçersiz olanların nasıl başarısız olduğunu ve nedenini anlamak artık kolay:
static_cast<D*>(b2s[0]) 0x7fffffffc910
: derleyici sadece denemek ve B2
içeren bir denemek için derleme zamanı bayt 0x10 gittiD
Ancak b2s[0]
bir a olmadığı için D
, şimdi tanımlanmamış bir hafıza bölgesine işaret ediyor.
Sökme:
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)
yani GCC'nin şunları yaptığını görüyoruz:
D
yok kidynamic_cast<D*>(b2s[0]) 0
: C ++ aslında kadronun geçersiz olduğunu ve geri döndüğünü buldu nullptr
!
Bunun derleme zamanında yapılmasının bir yolu yoktur ve bunu sökme işleminden doğrulayacağız:
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)
İlk olarak bir NULL kontrolü vardır ve çıkış NULL ise NULL döndürür.
Aksi takdirde, RDX, RSI ve RDI ve çağrılarda bazı argümanlar kurar __dynamic_cast
.
Bunu daha fazla analiz etmek için sabrım yok, ancak diğerlerinin söylediği gibi, bunun çalışmasının tek yolu __dynamic_cast
, sınıf hiyerarşisini temsil eden bazı ekstra RTTI bellek içi veri yapılarına erişmektir.
Bu nedenle B2
, bu tablonun girişinden başlamalı , sonra bu tür hiyerarşisinde bir tür D
tahmini için vtable'ı bulana kadar yürümelidir b2s[0]
.
Bu yüzden dökümün yeniden yorumlanması potansiyel olarak pahalıdır! İşte bir dönüştürücü bir astar yama örnek dynamic_cast
a static_cast
karmaşık bir projede% 33 çalışma zamanı düşük! .
reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940
bu sadece bize körü körüne inanıyor: bir D
adres olduğunu söyledik b2s[1]
ve derleyici ofset hesaplama yapmıyor.
Ancak bu yanlıştır, çünkü D aslında 0x7fffffffc930'dadır, 0x7fffffffc940'ta olan şey D içindeki B2 benzeri yapıdır! Böylece çöp kutusuna erişilir.
Bunu -O0
, değeri sadece hareket ettiren korkunç montajdan doğrulayabiliriz:
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)
İlgili sorular:
Ubuntu 18.04 amd64, GCC 7.4.0 üzerinde test edilmiştir.