के उचित उपयोग क्या हैं:
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
करने के लिए 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
सबसे खतरनाक कास्ट है, और इसे बहुत कम इस्तेमाल किया जाना चाहिए। यह एक प्रकार को सीधे दूसरे में बदल देता है - जैसे कि एक पॉइंटर से दूसरे में मूल्य डालना, या एक पॉइंटर को स्टोर करना int
, या अन्य सभी प्रकार की गंदी चीजों को। मोटे तौर पर, आपके पास एकमात्र गारंटी reinterpret_cast
यह है कि आम तौर पर यदि आप परिणाम को मूल प्रकार में वापस लाते हैं, तो आपको सटीक समान मूल्य मिलेगा (लेकिन यह नहीं कि मध्यवर्ती प्रकार मूल प्रकार से छोटा है)। ऐसे कई रूपांतरण हैं, जो reinterpret_cast
नहीं कर सकते हैं। यह विशेष रूप से अजीब रूपांतरण और बिट जोड़तोड़ के लिए उपयोग किया जाता है, जैसे कि कच्चे डेटा स्ट्रीम को वास्तविक डेटा में बदलना, या डेटा को संरेखित करने के लिए पॉइंटर के कम बिट्स में डेटा संग्रहीत करना।
सी-शैली डाली और समारोह शैली डाली का उपयोग कर डाले हैं (type)object
या type(object)
, क्रमशः, और कार्यात्मक रूप से बराबर कर रहे हैं। उन्हें निम्नलिखित में से पहले के रूप में परिभाषित किया गया है जो सफल होता है:
const_cast
static_cast
(हालांकि उपयोग प्रतिबंधों की अनदेखी)static_cast
(ऊपर देखें), फिर const_cast
reinterpret_cast
reinterpret_cast
, फिर const_cast
इसलिए इसे कुछ उदाहरणों में अन्य जातियों के प्रतिस्थापन के रूप में इस्तेमाल किया जा सकता है, लेकिन एक में विकसित करने की क्षमता के कारण बेहद खतरनाक हो सकता है 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
अक्सर एपीआई का सेट अपारदर्शी डेटा प्रकारों के साथ काम करते समय पसंद का हथियार होता है
dynamic_cast
एक विरासत पदानुक्रम के भीतर संकेत / संदर्भ परिवर्तित करने के लिए उपयोग करें ।
static_cast
साधारण प्रकार के रूपांतरणों के लिए उपयोग करें ।
reinterpret_cast
बिट पैटर्न के निम्न-स्तरीय पुनर्व्याख्या के लिए उपयोग करें । अत्यधिक सावधानी के साथ उपयोग करें।
const_cast
दूर कास्टिंग के लिए उपयोग करें const/volatile
। इससे बचें जब तक कि आप एक कास्ट-गलत एपीआई का उपयोग करके अटक नहीं जाते हैं।
(ऊपर बहुत सैद्धांतिक और वैचारिक व्याख्या दी गई है)
नीचे में से कुछ हैं व्यावहारिक उदाहरण है जब मैं इस्तेमाल किया 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&
।dynamic_cast
(Base*)
करने के लिए (Derived*)
विफल हो सकता है अगर सूचक व्युत्पन्न प्रकार की वास्तव में नहीं है।A*
लिए B*
, यदि कास्ट अमान्य है, तो डायनेमिक_कास्ट nullptr वापस आ जाएगा।A&
करने के लिए B&
करता है, तो डाली अमान्य है तो dynamic_cast bad_cast अपवाद फेंक देते हैं।const_cast
set<T>
कि केवल अपने तत्वों को कॉन्स्टेंट के रूप में देता है ताकि यह सुनिश्चित हो सके कि आप इसकी कुंजी को नहीं बदलते हैं। हालाँकि यदि आपका इरादा ऑब्जेक्ट के गैर-प्रमुख सदस्यों को संशोधित करना है तो यह ठीक होना चाहिए। आप कब्ज को दूर करने के लिए 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.
आप यूबी प्राप्त करते हैं जिसके परिणामस्वरूप यदि आप भाग्यशाली हैं तो रनगेट पर सेगफॉल्ट हो सकता है। 2. डायनामिक कास्ट का उपयोग क्रॉस कास्टिंग में भी किया जा सकता है। 3. कास्ट कास्ट कुछ मामलों में यूबी में परिणाम कर सकते हैं। 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**
)। मान लें कि आप इस फ़ंक्शन से ऑब्जेक्ट के विशेष वर्ग का अनुरोध करते हैं, तो आप पहले से ही सूचक के लिए प्रकार जानते हैं (जो आप अक्सर कॉम ऑब्जेक्ट्स के लिए करते हैं)। इस मामले में आप में अपने सूचक को सूचक डाली नहीं कर सकते हैं 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)
?
हालांकि अन्य जवाबों ने सी ++ जातियों के बीच सभी अंतरों का अच्छी तरह से वर्णन किया है, मैं एक लघु नोट जोड़ना चाहूंगा कि आपको सी-स्टाइल कलाकारों (Type) var
और का उपयोग क्यों नहीं करना चाहिए Type(var)
।
C ++ शुरुआती के लिए C- शैली की जातियां C ++ जातियों (static_cast <>), डायनामिक_कास्ट <> (), const_cast <> (), reinterpret_cast <> () पर सुपरसेट ऑपरेशन की तरह दिखती हैं और कोई भी व्यक्ति उन्हें C ++ जातियों के ऊपर पसंद कर सकता है। । वास्तव में सी-स्टाइल कास्ट सुपरसेट और लिखने के लिए छोटा है।
सी-शैली के कलाकारों की मुख्य समस्या यह है कि वे डेवलपर को कलाकारों के वास्तविक इरादे को छिपाते हैं। C- शैली की जातियां लगभग सभी प्रकार की कास्टिंग कर सकती हैं, जो आमतौर पर static_cast <> () और डायनामिक_कास्ट <() द्वारा की जाती हैं जैसे कि const_cast <> () के लिए संभावित खतरनाक जातियाँ, जहाँ const modifier को हटाया जा सकता है ताकि 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- शैली वाले कलाकारों को प्राथमिकता देना चाहिए।
यहाँ Bjarne Stroustrup (C ++ के लेखक) की पुस्तक C ++ प्रोग्रामिंग लैंग्वेज 4th संस्करण का एक छोटा उद्धरण है - पृष्ठ 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) त्रुटि के बिना संकलित होती है। केवल रीइंटरटेनमेंट_कास्ट का उपयोग पॉइंटर को ऑब्जेक्ट से पॉइंटर को किसी असंबंधित ऑब्जेक्ट प्रकार में बदलने के लिए किया जा सकता है।
इस पर ध्यान दिया जाना है: डायनामिक_कास्ट रन-टाइम में विफल हो जाएगा, हालांकि अधिकांश कंपाइलरों पर यह संकलित करने में भी विफल हो जाएगा क्योंकि पॉइंटर को डाले जाने की संरचना में कोई वर्चुअल फ़ंक्शन नहीं हैं, जिसका अर्थ है कि डायनेमिक_कास्ट केवल पॉलीफ़ोरफ़िक क्लास पॉइंटर्स के साथ काम करेगा ।
C ++ कास्ट का उपयोग कब करें :
static_cast
बनाम dynamic_cast
बनाम reinterpret_cast
आंतरिक एक डाउनकास्ट / upcast पर देखें
इस उत्तर में, मैं इन तीन तंत्रों की तुलना एक ठोस अपकास्ट / डाउनकास्ट उदाहरण पर करता हूं और विश्लेषण करता हूं कि अंतर्निहित बिंदुओं / मेमोरी / असेंबली का क्या होता है, इसकी ठोस समझ देने के लिए कि वे कैसे तुलना करते हैं।
मेरा मानना है कि इससे उन जातियों के अलग होने का एक अच्छा अंतर्ज्ञान होगा:
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
, और इसलिए सभी आभासी कॉल पारदर्शी रूप से काम करते हैं।
अब, हम आखिरकार टाइपिंग कास्टिंग और हमारे ठोस उदाहरण के विश्लेषण के लिए वापस आ सकते हैं।
स्टडआउट आउटपुट से हम देखते हैं:
&d 0x7fffffffc930
b2s[1] 0x7fffffffc940
इसलिए, static_cast
वहां किए गए निहितार्थ ने D
0x7fffffc930 पर पूर्ण डेटा संरचना से ऑफसेट की गणना ठीक उसी B2
तरह की है जो 0x7fffffc940 पर है। हम यह भी अनुमान लगाते हैं कि 0x7fffffc930 और 0x7fffffffc940 के बीच जो निहित है, वह संभवतः B1
डेटा और वाइब्रेट हो सकता है ।
फिर, डाउनकास्ट अनुभागों पर, अब यह समझना आसान है कि अमान्य कैसे विफल होते हैं और क्यों:
static_cast<D*>(b2s[0]) 0x7fffffffc910
: कंपाइलर केवल 0x10 पर संकलित समय बाइट्स में कोशिश करने और एक B2
से युक्त होने के लिए ऊपर चला गयाD
लेकिन क्योंकि b2s[0]
यह नहीं था D
, यह अब एक अपरिभाषित स्मृति क्षेत्र की ओर इशारा करता है।
Disassembly है:
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)
इसलिए हम देखते हैं कि जीसीसी करता है:
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 है।
अन्यथा, यह आरडीएक्स, आरएसआई और आरडीआई और कॉल में कुछ तर्क देता है __dynamic_cast
।
मेरे पास अब इसे और अधिक विश्लेषण करने का धैर्य नहीं है, लेकिन जैसा कि अन्य ने कहा, इसके लिए काम करने का एकमात्र तरीका __dynamic_cast
कुछ अतिरिक्त आरटीटीआई-इन-मेमोरी डेटा संरचनाओं का उपयोग करना है जो वर्ग पदानुक्रम का प्रतिनिधित्व करते हैं।
इसलिए इसे B2
उस तालिका के लिए प्रविष्टि से शुरू करना चाहिए , फिर इस वर्ग पदानुक्रम तक चलना चाहिए जब तक कि यह पता नहीं चलता कि D
टाइप टाइपकास्ट के लिए व्यवहार्य है b2s[0]
।
यही कारण है कि पुनर्व्याख्या कास्ट संभावित रूप से महंगा है! यहाँ एक उदाहरण है जहाँ एक जटिल प्रक्रिया में एक में एक लाइनर पैच को परिवर्तित dynamic_cast
करने से static_cast
रनटाइम में 33% की कमी आई है! ।
reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940
यह सिर्फ हमें आँख बंद करके विश्वास करता है: हमने कहा कि एक D
पते पर है b2s[1]
, और संकलक कोई ऑफसेट गणना नहीं करता है।
लेकिन यह गलत है, क्योंकि डी वास्तव में 0x7fffffc930 पर है, जो 0x7fffffffc940 पर है वह डी के अंदर बी 2 जैसी संरचना है! तो कचरा पहुंच जाता है।
हम इस भयावह -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 am6464, GCC 7.4.0 पर परीक्षण किया गया।