2012-07-31 9 views
27

मैं जानना चाहता हूं कि डिलीट ऑपरेटर मेमोरी लोकेशन को कैसे हटाता है जिसे ऑब्जेक्ट के वास्तविक स्मृति स्थान से अलग बेस क्लास पॉइंटर दिया जाता है, जिसे मुक्त किया जाना चाहिए।सी ++ डिलीट ऑपरेटर को पॉलिमॉर्फिक ऑब्जेक्ट का मेमोरी लोकेशन कैसे मिलता है?

मैं इस व्यवहार को अपने स्वयं के कस्टम आवंटक/डेलोकेटर में डुप्लिकेट करना चाहता हूं।

निम्न पदानुक्रम पर विचार करें:

struct A 
{ 
    unsigned a; 
    virtual ~A() { } 
}; 

struct B 
{ 
    unsigned b; 
    virtual ~B() { } 
}; 

struct C : public A, public B 
{ 
    unsigned c; 
}; 

मैं प्रकार सी की एक वस्तु का आवंटन और के रूप में मैं बता सकता है इस ऑपरेटर का एक मान्य उपयोग हटाना है जहां तक ​​प्रकार बी के एक सूचक के माध्यम से इसे नष्ट करना चाहते, और यह लिनक्स/जीसीसी के तहत काम करता है:

C* c = new C; 
B* b = c; 

delete b; 

दिलचस्प बात यह है कि संकेत 'बी' और 'सी' वास्तव में की वजह से अलग-अलग पतों कैसे वस्तु स्मृति में खर्च की गई थी, और नष्ट ऑपरेटर "को इंगित है जानता है "सही मेमोरी स्थान कैसे ढूंढें और कैसे मुक्त करें।

मुझे पता है कि, सामान्य रूप से, बेस क्लास पॉइंटर: Find out the size of a polymorphic object दिए गए पॉलीमोर्फिक ऑब्जेक्ट का आकार ढूंढना संभव नहीं है। मुझे संदेह है कि वस्तु के वास्तविक स्मृति स्थान को ढूंढना आम तौर पर संभव नहीं है।

नोट्स:

  • मेरे सवाल यह है कि नई [] और [] हटाने के काम से संबंधित नहीं है। मुझे एक ऑब्जेक्ट आवंटन मामले में रूचि है। How does delete[] "know" the size of the operand array?
  • मुझे इस बारे में कोई चिंता नहीं है कि विनाशक को या तो कैसे कहा जाता है। मुझे स्मृति की गिरावट में दिलचस्पी है। How 'delete' works when I delete a pointer of base class
  • मैंने -फनो-आरटीआई और -फनो-अपवादों का उपयोग करके परीक्षण किया, इसलिए G ++ को रनटाइम प्रकार की जानकारी तक पहुंच नहीं होनी चाहिए।
+0

संक्षिप्त उत्तर: मानक के अलावा यह कार्यान्वयन परिभाषित किया गया है कि एक अनुरूप कंपेलर को इसका समर्थन करना चाहिए। मेरा अनुमान है कि, हालांकि, जी ++ आंतरिक विनाश तर्क के कुछ हिस्से में कूदने के लिए vtable का उपयोग करता है जो ठीक से काम करेगा। –

+0

हालांकि कुछ कार्यान्वयन देखना दिलचस्प होगा। (सी में 'मॉलोक/फ्री' सी समान है, और मुझे विश्वास है कि दृष्टिकोण पर 'मॉलोक' से लौटने वाले पॉइंटर को "स्पेस" से पहले कुछ जगह आरक्षित करना है।) –

+0

हालांकि, मुझे 'बी नहीं मिलता है! = सी' .. अलग-अलग चर हैं लेकिन एक ही ऑब्जेक्ट (मेमोरी) दोनों "बिंदु", नहीं? (मैं सी ++ का उपयोग नहीं करता, इसलिए जादू मुझ पर खो गया है)। –

उत्तर

13

यह स्पष्ट रूप से कार्यान्वयन विशिष्ट है। व्यावहारिक रूप से चीजों को लागू करने के लिए अपेक्षाकृत कम संख्या में समझदार तरीके हैं। सैद्धांतिक रूप से वहाँ कुछ समस्याओं यहां हैं:

  1. आप सबसे व्युत्पन्न वस्तु के लिए एक सूचक प्राप्त करने में सक्षम होने की जरूरत है, कि उद्देश्य यह है कि (धारणात्मक) अन्य प्रकार के सभी शामिल है।

    मानक सी में आप एक dynamic_cast साथ ऐसा कर सकते ++:

    void *derrived = dynamic_cast<void*>(some_ptr); 
    

    कौन सा C* सिर्फ एक B* से वापस हो जाता है, उदाहरण के लिए:

    #include <iostream> 
    
    struct A 
    { 
        unsigned a; 
        virtual ~A() { } 
    }; 
    
    struct B 
    { 
        unsigned b; 
        virtual ~B() { } 
    }; 
    
    struct C : public A, public B 
    { 
        unsigned c; 
    }; 
    
    int main() { 
        C* c = new C; 
        std::cout << static_cast<void*>(c) << "\n"; 
        B* b = c; 
        std::cout << static_cast<void*>(b) << "\n"; 
        std::cout << dynamic_cast<void*>(b) << "\n"; 
    
        delete b; 
    } 
    

    अपने सिस्टम

    पर निम्नलिखित दी
     
    0x912c008 
    0x912c010 
    0x912c008 
    
  2. एक बार यह किया जाता है तो एक मानक स्मृति आवंटन ट्रैकिंग समस्या बन जाता है। आम तौर पर यह दो तरीकों में से एक में किया जाता है, या तो ए) आवंटित स्मृति से पहले आवंटन के आकार को रिकॉर्ड करें, आकार ढूंढना सिर्फ एक सूचक घटाव है या बी) किसी प्रकार की डेटा संरचना में आवंटन और मुफ्त मेमोरी रिकॉर्ड करें। अधिक जानकारी के लिए this question देखें, जिसमें एक अच्छा संदर्भ है।

    glibc के साथ आप काफी समझदारी से किसी दिए गए आवंटन के आकार को क्वेरी कर सकते हैं: जानकारी के लिए स्वतंत्र के लिए उपलब्ध है

    #include <iostream> 
    #include <stdlib.h> 
    #include <malloc.h> 
    
    int main() { 
        char *test = (char*)malloc(50); 
        std::cout << malloc_usable_size(test) << "\n"; 
    } 
    

    कि/इसी तरह और क्या स्मृति के लिए लौट आए हिस्सा के साथ क्या करना यह पता लगाने के लिए इस्तेमाल किया हटा दें।

malloc_useable_size के कार्यान्वयन के समुचित कारण libc स्रोत कोड में दिए गए हैं, malloc/malloc.c में: (। कॉलिन साहुल द्वारा निम्नलिखित हल्के से संपादित भी शामिल स्पष्टीकरण)

मेमोरी के टुकड़े 'सीमा टैग' विधि का उपयोग करके बनाए रखा जाता है जैसे उदाहरण के लिए, Knuth या Standish। (इस तरह के तकनीकों के सर्वेक्षण के लिए पॉल विल्सन ftp://ftp.cs.utexas.edu/pub/garbage/allocsrv.ps द्वारा पेपर देखें।) मुक्त हिस्सों के आकार के सामने प्रत्येक छोर पर और अंत में संग्रहीत किए जाते हैं। यह खंडित हिस्सों को को बड़े हिस्सों में बहुत तेजी से समेकित करता है। आकार के फ़ील्ड बिट्स भी दर्शाते हैं कि भाग मुक्त हैं या उपयोग में हैं या नहीं।

एक आवंटित हिस्सा इस तरह दिखता है:

 
    chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 
      |    Size of previous chunk, if allocated   | | 
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 
      |    Size of chunk, in bytes      |M|P| 
     mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 
      |    User data starts here...       . 
      .                . 
      .    (malloc_usable_size() bytes)      . 
      .                | 
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+  
      |    Size of chunk          | 
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 
+0

मैं इस बात को विस्तारित कर सकता हूं कि यदि आप रुचि रखते हैं तो 'गतिशील_कास्ट ' लागू किया गया है, अवधारणात्मक रूप से यह काम करने के लिए आवश्यक है। – Flexo

+1

गतिशील_कास्ट चाल वास्तव में साफ है! ऐसा लगता है कि -फनो-आरटीआई के साथ भी काम करता है, लेकिन मुझे आश्चर्य है कि यह कितना पोर्टेबल है? – Anton

+0

मैं स्मृति आवंटन ट्रैकिंग के बारे में स्पष्टीकरण की सराहना करता हूं, हालांकि यह वास्तव में मेरे प्रश्न का हिस्सा नहीं था। – Anton

10

बेस क्लास पॉइंटर को नष्ट करने की आवश्यकता है कि आपने वर्चुअल विनाशक को लागू किया हो। यदि आपने ऐसा नहीं किया है, तो सभी दांव बंद हैं।

वर्चुअल तंत्र (vtable) द्वारा निर्धारित सबसे पहले व्युत्पन्न वस्तु के लिए कहा जाने वाला पहला विनाशक होगा। यह विनाशक वस्तु के आकार को जानता है! यह उस जानकारी को किसी जगह से गिलहरी कर सकता है, या इसे विनाशकों की श्रृंखला से गुजर सकता है।

0

एक बहुरूपी वस्तु के लिए एक सूचक आमतौर पर ऑब्जेक्ट के लिए एक सूचक और आभासी मेज, जो वस्तु की मूल वर्ग के बारे में जानकारी शामिल है के रूप में कार्यान्वित किया जाता है । इन कार्यान्वयन विवरण पता है और मिलेगा सही नाशक हटाना

6

इसके कार्यान्वयन में परिभाषित किया गया है, लेकिन एक सामान्य कार्यान्वयन तकनीक है कि operator delete वास्तव में नाशक (बजाय उस में delete साथ कोड) द्वारा कहा जाता है, और वहाँ एक छिपे हुए पैरामीटर है विनाशक को यह नियंत्रित करता है कि operator delete कहलाता है या नहीं।

इस कार्यान्वयन के साथ, विनाशक (सभी स्पष्ट डॉटोर कॉल, ऑटो और स्थिर चर के लिए कॉल, और व्युत्पन्न विनाशकों से आधार विनाशकों को कॉल करने के लिए कॉल) के लिए अधिकतर कॉलों को उस अतिरिक्त छिपे हुए तर्क को गलत पर सेट किया जाएगा (इसलिए ऑपरेटर डिलीट नहीं होगा बुलाया जाए)। जब एक डिलीवरी अभिव्यक्ति होती है, हालांकि, यह ऑब्जेक्ट के लिए छुपे हुए तर्क के साथ शीर्ष-स्तर के विनाशक को कॉल करता है।आपके उदाहरण में, यह सी :: ~ सी() होगा, इसलिए यह पूरे ऑब्जेक्ट

+0

तो संभवतः 'सी :: ~ सी (सत्य) '' बी :: ~ बी (झूठी)', फिर 'ए :: ~ ए (झूठी) 'कॉल करेगा, फिर हटाएं (यह, आकार (सी))' ? –

+0

मैंने बहुत अधिक माना था कि समाधान कार्यान्वयन-विशिष्ट था, और यह आपके वर्णन के बारे में बताता है कि यह कैसे काम करता है। क्या कोई दस्तावेज है जो इस कार्यान्वयन का विस्तार से वर्णन करता है? – Anton

+1

कार्यान्वयन विवरणों के बारे में जानने के लिए आपको कार्यान्वयन के लिए दस्तावेज देखना होगा। कुछ कार्यों के लिए मुझे पता है कि एकमात्र कार्यान्वयन इस तरह से कुछ लेख पढ़ने से मूल सीफ्रंट है। हालांकि, सीफ्रंट से कई कार्यान्वयन तकनीक अन्य कंपाइलरों में समाप्त हो गईं। –

0

के लिए मेमोरी को पुनः प्राप्त करने के बारे में पता चलेगा। यह वही तरीका मैलोक करता है। कुछ मॉलॉक्स ऑब्जेक्ट से पहले ही आकार को रिकॉर्ड करते हैं। अधिकांश आधुनिक मॉलॉक्स बहुत अधिक परिष्कृत हैं। tcmalloc देखें, एक तेज़ आवंटक जो पृष्ठों पर एक ही आकार की वस्तुओं को एक साथ रखता है ताकि इसे केवल पृष्ठ ग्रैन्युलरिटी पर आकार की जानकारी रखने की आवश्यकता हो।

1

सामान्य कार्यान्वयन (सैद्धांतिक रूप से, अन्य लोग हो सकते हैं, मुझे संदेह है कि अभ्यास में हैं) यह है कि प्रति मूल वस्तु एक vtable है (यदि नहीं, आधार वस्तुएं polymorph नहीं हैं और इसे हटाने के लिए उपयोग नहीं किया जा सकता है) । उस vtable में न केवल आभासी कार्यों के लिए सूचक है, बल्कि पूरे आरटीटीआई के लिए भी क्या आवश्यक है, वर्तमान वस्तु से ऑफसेट को सबसे व्युत्पन्न में शामिल किया गया है।

struct A_VTable_Desc { 
    int offset; 
    void* (destructor)(); 
} AVTable = { 0, A::~A }; 

struct A_impl { 
    unsigned a; 
    A_VTable_Desc* vptr; 
}; 

struct B_VTable_Desc { 
    int offset; 
    void* (destructor)(); 
} BVtable = { 0, &B::~B }; 

struct B_impl { 
    unsigned b; 
    B_VTable_Desc* __vptr; 
}; 

A_VTable_Desc CAVtable = { 0, &C::~C_as_A }; 
B_VTable_Desc CBVtable = { -8, &C::~C_as_B }; 

struct C { 
    A_impl __aimpl; 
    B_impl __bimpl; 
    unsigned c; 
}; 

और सी के कंस्ट्रक्टर्स परोक्ष

की तरह कुछ कार्य करें:

आदेश की व्याख्या करने में (वहाँ शायद किसी भी सच्चे कार्यान्वयन में मतभेद हैं और मैं कुछ त्रुटियाँ बना दिया है सकते हैं), यहाँ क्या वास्तव में इस्तेमाल किया जाता है

this->__aimpl->__vptr = &CAVtable; 
this->__bimpl->__vptr = &CBVtable; 
1

जब delete ऑपरेटर संकलन, संकलक एक 'आवंटन रद्द करने' समारोह निर्धारित करता है के बाद नाशक निष्पादित किया जाता है कॉल करने के लिए की जरूरत है। ध्यान दें कि विनाशक के पास डीलोकेशन कॉल के साथ सीधे कुछ भी नहीं है, लेकिन इसका असर पड़ता है कि संकलनकर्ता द्वारा डिलीओशन फ़ंक्शन को कैसे देखा जाता है।

सामान्य मामले में, वस्तु के लिए कोई विशेष प्रकार के आवंटन रद्द करने समारोह, जिस स्थिति में वैश्विक आवंटन रद्द समारोह प्रयोग किया जाता है और जो हमेशा परोक्ष घोषित किया जाता है (सी ++ 03 3.7.3/2):

void operator delete(void*) throw(); 

ध्यान दें कि यह फ़ंक्शन आकार तर्क भी नहीं लेता है। इसे पॉइंटर के मूल्य के अलावा कुछ भी नहीं के आधार पर आवंटन आकार निर्धारित करने की आवश्यकता है। यह पता से पहले आवंटन के आकार को संग्रहीत करके किया जा सकता है (क्या कोई कार्यान्वयन है जो इसे किसी अन्य तरीके से करता है?)।

हालांकि, उस डेलोकेशन फ़ंक्शन का उपयोग करने का निर्णय लेने से पहले, कंपाइलर यह देखने के लिए एक लुकअप करता है कि एक प्रकार-विशिष्ट डेलोकेशन फ़ंक्शन का उपयोग किया जाना चाहिए या नहीं। उस फ़ंक्शन में एकल पैरामीटर हो सकता है (void*) या दो पैरामीटर (void* और size_t)।

जब आवंटन रद्द समारोह को देख, अगर सूचक के स्थिर प्रकार delete को संकार्य के रूप में इस्तेमाल एक आभासी नाशक, तो (सी ++ 03 12.5/4) है:

आवंटन रद्द समारोह है गतिशील प्रकार की आभासी नाशक

प्रभाव में की परिभाषा में देखने के द्वारा पाया, तो कोई भी operator delete() आवंटन रद्द समारोह प्रकार एक आभासी नाशक होती हैं, के आभासी है, भले ही वास्तविक समारोह static होना चाहिए (स्टेशन ndard 12.5/7 में यह नोट करता है)। इस मामले में, कंपाइलर ऑब्जेक्ट के आकार को पास कर सकता है यदि उसे इसकी आवश्यकता होती है क्योंकि इसकी ऑब्जेक्ट के गतिशील प्रकार तक पहुंच है (ऑब्जेक्ट पॉइंटर को कोई भी आवश्यक समायोजन उसी तरह मिल सकता है)।

यदि ऑपरेटिंग का स्थिर प्रकार delete स्थिर है, तो operator delete() डिलीओशन फ़ंक्शन के लिए लुकअप सामान्य नियमों का पालन करता है।दोबारा, यदि कंपाइलर एक डेलोकेशन फ़ंक्शन का चयन करता है जिसे आकार पैरामीटर की आवश्यकता होती है, तो ऐसा इसलिए हो सकता है क्योंकि यह संकलन समय पर ऑब्जेक्ट के स्थिर प्रकार को जानता है।

अंतिम स्थिति वह है जो अपरिभाषित व्यवहार में परिणाम देती है: यदि पॉइंटर के स्थिर प्रकार में वर्चुअल विनाशक नहीं है लेकिन व्युत्पन्न प्रकार ऑब्जेक्ट को इंगित करता है, तो संकलक संभावित रूप से गलत डेलोकेशन फ़ंक्शन को देखेगा और गलत आकार को पास करेगा । लेकिन चूंकि यह अनिर्धारित व्यवहार का नतीजा है, इससे कोई फर्क नहीं पड़ता।

संबंधित मुद्दे