C ++ में कचरा क्या होता है?


51

जावा में एक स्वचालित जीसी है जो एक बार स्टॉप द वर्ल्ड में है, लेकिन एक ढेर पर कचरे की देखभाल करता है। अब C / C ++ एप्लिकेशन में ये STW फ्रीज़ नहीं हैं, उनकी मेमोरी का उपयोग असीम रूप से नहीं बढ़ता है। यह व्यवहार कैसे प्राप्त किया जाता है? मृत वस्तुओं की देखभाल कैसे की जाती है?


38
नोट: स्टॉप-द-वर्ल्ड कुछ कचरा संग्रहकर्ताओं का कार्यान्वयन विकल्प है, लेकिन निश्चित रूप से सभी नहीं। उदाहरण के लिए, समवर्ती जीसी हैं, जो म्यूटेटर के साथ समवर्ती रूप से चलते हैं (यही जीसी डेवलपर्स वास्तविक कार्यक्रम कहते हैं)। मेरा मानना ​​है कि आप आईबीएम के खुले स्रोत जेवीएम जे 9 का एक व्यावसायिक संस्करण खरीद सकते हैं जिसमें एक समवर्ती पॉज़लेस कलेक्टर है। अज़ुल ज़िंग में एक "पॉज़लेस" कलेक्टर है जो वास्तव में पॉज़लेस नहीं है, लेकिन बहुत तेज़ है ताकि कोई ध्यान देने योग्य पॉज़ न हो (इसके जीसी पॉज़ ऑपरेटिंग सिस्टम थ्रेड संदर्भ स्विच के रूप में एक ही क्रम पर हैं, जिसे आमतौर पर पॉज़ के रूप में नहीं देखा जाता है) ।
जोर्ज डब्ल्यू मित्तग

14
ज्यादातर (लंबे समय से चलने वाले) C ++ प्रोग्राम जो मैं उपयोग करता हूं उनमें मेमोरी का उपयोग होता है जो समय के साथ बढ़ता है। क्या यह संभव है कि आप एक समय में कुछ दिनों से अधिक समय तक खुले रहने वाले कार्यक्रमों को छोड़ने की आदत में नहीं हैं?
जोनाथन

12
यह ध्यान रखें कि आधुनिक C ++ और इसके निर्माणों के साथ आपको अब या तो मेमोरी को मैन्युअल रूप से हटाने की आवश्यकता नहीं है (जब तक कि आप कुछ विशेष अनुकूलन के बाद नहीं हैं), क्योंकि आप स्मार्ट पॉइंटर्स के माध्यम से डायनामिक मेमोरी को प्रबंधित कर सकते हैं। जाहिर है, यह C ++ विकास में कुछ ओवरहेड जोड़ता है और आपको थोड़ा अधिक सावधान रहने की आवश्यकता है, लेकिन यह पूरी तरह से अलग बात नहीं है, आपको बस कॉलिंग मैनुअल के बजाय स्मार्ट पॉइंटर निर्माण का उपयोग करने के लिए याद रखना होगा new
एंडी

9
ध्यान दें कि कचरा संग्रहित भाषा में मेमोरी लीक होना अभी भी संभव है। मैं जावा से अपरिचित हूं, लेकिन स्मृति लीक दुर्भाग्य से .NET की प्रबंधित, जीसी दुनिया में काफी आम है। एक स्थैतिक क्षेत्र द्वारा अप्रत्यक्ष रूप से संदर्भित वस्तुओं को स्वचालित रूप से एकत्र नहीं किया जाता है, इवेंट हैंडलर लीक का एक बहुत ही सामान्य स्रोत हैं, और कचरा संग्रह का गैर-नियतात्मक प्रकृति इसे मैन्युअल रूप से मुक्त संसाधनों की आवश्यकता को पूरी तरह से समाप्त करने में असमर्थ बनाता है (जिसके कारण IDis प्रयोज्य की ओर अग्रसर होता है) पैटर्न)। सभी ने कहा, C ++ मेमोरी मैनेजमेंट मॉडल का सही इस्तेमाल कचरा संग्रहण से कहीं बेहतर है।
कॉडी ग्रे

26
What happens to garbage in C++? क्या यह आमतौर पर एक निष्पादन योग्य में संकलित नहीं है?
बीजे मायर्स

जवाबों:


100

प्रोग्रामर यह सुनिश्चित करने के लिए ज़िम्मेदार है कि उनके द्वारा बनाई गई वस्तुओं को माध्यम newसे हटा दिया जाता है delete। यदि कोई ऑब्जेक्ट बनाया जाता है, लेकिन अंतिम पॉइंटर या संदर्भ से पहले नष्ट नहीं किया जाता है, तो यह गुंजाइश से बाहर हो जाता है, यह दरारें से गिरता है और मेमोरी लीक बन जाता है ।

दुर्भाग्य से सी, सी ++ और अन्य भाषाओं के लिए, जिसमें जीसी शामिल नहीं है, यह बस समय के साथ ढेर हो जाता है। यह एक एप्लिकेशन या सिस्टम को मेमोरी से बाहर चलाने का कारण बन सकता है और मेमोरी के नए ब्लॉक को आवंटित करने में असमर्थ हो सकता है। इस बिंदु पर, उपयोगकर्ता को एप्लिकेशन को समाप्त करने का सहारा लेना चाहिए ताकि ऑपरेटिंग सिस्टम उस प्रयुक्त मेमोरी को पुनः प्राप्त कर सके।

जहाँ तक इस समस्या को कम करने की बात है, ऐसी कई चीजें हैं जो प्रोग्रामर के जीवन को बहुत आसान बनाती हैं। ये मुख्य रूप से स्कोप की प्रकृति द्वारा समर्थित हैं ।

int main()
{
    int* variableThatIsAPointer = new int;
    int variableInt = 0;

    delete variableThatIsAPointer;
}

यहाँ, हमने दो चर बनाए। वे ब्लॉक स्कोप में मौजूद हैं , जैसा कि {}घुंघराले ब्रेसिज़ द्वारा परिभाषित किया गया है। जब निष्पादन इस दायरे से बाहर हो जाता है, तो ये ऑब्जेक्ट स्वचालित रूप से हटा दिए जाएंगे। इस मामले में variableThatIsAPointer, जैसा कि इसके नाम का तात्पर्य है, स्मृति में किसी वस्तु का सूचक है। जब यह कार्यक्षेत्र से बाहर हो जाता है, तो पॉइंटर को हटा दिया जाता है, लेकिन यह जिस वस्तु की ओर इशारा करता है वह बनी रहती है। यहां, हम deleteइस ऑब्जेक्ट को स्कोप लीक होने से पहले यह सुनिश्चित करने के लिए गुंजाइश से बाहर कर देते हैं कि कोई मेमोरी लीक तो नहीं है। हालाँकि हम इस पॉइंटर को कहीं और पास कर सकते हैं और उम्मीद करते हैं कि इसे बाद में डिलीट कर दिया जाएगा।

दायरे की यह प्रकृति कक्षाओं तक फैली हुई है:

class Foo
{
public:
    int bar; // Will be deleted when Foo is deleted
    int* otherBar; // Still need to call delete
}

यहाँ, एक ही सिद्धांत लागू होता है। डिलीट barहोने पर हमें परेशान होने की जरूरत नहीं है Foo। हालाँकि otherBar, केवल सूचक हटा दिया गया है। अगर otherBarयह जिस भी वस्तु की ओर इशारा करता है, उसका एकमात्र वैध सूचक है, तो हमें संभवतः deleteइसे Fooनष्ट करना चाहिए । यह RAII के पीछे ड्राइविंग अवधारणा है

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

RAII स्मार्ट पॉइंटर्स के पीछे विशिष्ट ड्राइविंग बल भी है । सी ++ स्टैंडर्ड लाइब्रेरी में, इन कर रहे हैं std::shared_ptr, std::unique_ptrऔर std::weak_ptr; हालाँकि मैंने अन्य अवधारणाओं को देखा shared_ptr/ उपयोग किया है weak_ptrजो समान अवधारणाओं का पालन करते हैं। इनके लिए, एक संदर्भ काउंटर यह बताता है कि किसी दिए गए ऑब्जेक्ट में कितने पॉइंट हैं, और स्वचालित रूप deleteसे ऑब्जेक्ट को एक बार देखने के बाद इसके संदर्भ नहीं हैं।

इसके अलावा, यह सब एक प्रोग्रामर के लिए उचित प्रथाओं और अनुशासन के लिए नीचे आता है ताकि यह सुनिश्चित किया जा सके कि उनका कोड वस्तुओं को ठीक से संभालता है।


4
के माध्यम से नष्ट delete- मैं क्या देख रहा था। बहुत बढ़िया।
जू शुआ

3
आप c ++ में दिए गए स्कोपिंग मैकेनिज्म के बारे में जोड़ना चाह सकते हैं जो बहुत कुछ नया करने की अनुमति देता है और ज्यादातर-स्वचालित बनाया जा सकता है।
whatsisname

9
@whatsisname यह नहीं है कि नए और डिलीट को स्वचालित बना दिया जाता है, यह है कि वे कई मामलों में बिल्कुल नहीं होते हैं
Caleth

10
deleteस्वचालित रूप से आप के लिए कहा जाता है स्मार्ट संकेत करता है, तो आप उन्हें प्रयोग ताकि आप उन्हें हर बार प्रयोग करने पर विचार करना चाहिए एक स्वत: भंडारण नहीं किया जा सकता है।
मैरियन स्पैनिक

11
@JuShua ध्यान दें कि आधुनिक C ++ लिखते समय, आपको कभी भी deleteअपने एप्लिकेशन कोड (और C ++ 14 के बाद, उसी के साथ new) की आवश्यकता नहीं होनी चाहिए , लेकिन इसके बजाय स्मार्ट पॉइंटर्स और RAII का उपयोग करके हटाए गए ऑब्जेक्ट्स को हटाने के लिए उपयोग करें। std::unique_ptrप्रकार और std::make_uniqueफ़ंक्शन अनुप्रयोग कोड स्तर पर प्रत्यक्ष newऔर सरल प्रतिस्थापन हैं delete
०४ पर

82

C ++ में कचरा संग्रह नहीं है।

C ++ अनुप्रयोगों को अपने स्वयं के कचरे का निपटान करने की आवश्यकता होती है।

इसे समझने के लिए C ++ एप्लिकेशन प्रोग्रामर की आवश्यकता होती है।

जब वे भूल जाते हैं, तो परिणाम को "मेमोरी लीक" कहा जाता है।


22
आपने निश्चित रूप से यह सुनिश्चित किया है कि आपके उत्तर में न तो कोई कचरा है और न ही बॉयलरप्लेट ...
वामावर्तबाउट

15
@leftaroundabout: धन्यवाद। मुझे लगता है कि एक तारीफ है।
जॉन आर। स्ट्रॉहम

1
इस कचरा-मुक्त उत्तर को खोजने के लिए एक कीवर्ड है: स्मृति रिसाव। यह भी किसी भी तरह का उल्लेख करने के लिए अच्छा होगा newऔर delete
रुस्लान

4
@Ruslan एक ही भी लागू होता है mallocऔर free, या new[]और delete[]किसी अन्य allocators (विंडोज की तरह, या GlobalAlloc, LocalAlloc, SHAlloc, CoTaskMemAlloc, VirtualAlloc, HeapAlloc, और स्मृति आप के लिए आवंटित, ...) (जैसे के माध्यम से fopen)।
user253751

43

गार्बेज कलेक्टर के बिना C, C ++ और अन्य प्रणालियों में, डेवलपर को भाषा और इसके पुस्तकालयों द्वारा सुविधाओं की पेशकश की जाती है ताकि यह इंगित किया जा सके कि मेमोरी को कब रिकवर किया जा सकता है।

सबसे बुनियादी सुविधा स्वचालित भंडारण है । कई बार, भाषा स्वयं यह सुनिश्चित करती है कि आइटम निपटाए जाएं:

int global = 0; // automatic storage

int foo(int a, int b) {
    static int local = 1; // automatic storage

    int c = a + b; // automatic storage

    return c;
}

इस मामले में, संकलक यह जानने का प्रभारी होता है कि कब उन मूल्यों का उपयोग नहीं किया जाता है और उनके साथ जुड़े भंडारण को पुनः प्राप्त करता है।

सी में गतिशील भंडारण का उपयोग करते समय , स्मृति को पारंपरिक रूप से आवंटित किया जाता है mallocऔर उसके साथ पुन: प्राप्त किया जाता है free। C ++ में, स्मृति को पारंपरिक रूप से आबंटित किया जाता है newऔर उसके साथ पुनः प्राप्त किया जाता है delete

सी पिछले कुछ वर्षों में बहुत अधिक नहीं बदला है, हालांकि आधुनिक सी ++ + बच जाता है newऔर deleteपूरी तरह से पुस्तकालय सुविधाओं पर निर्भर करता है (जो स्वयं उपयोग newऔर deleteउचित रूप से):

  • स्मार्ट पॉइंटर्स सबसे प्रसिद्ध हैं: std::unique_ptrऔरstd::shared_ptr
  • : लेकिन कंटेनर और अधिक बड़े पैमाने पर वास्तव में कर रहे std::string, std::vector, std::map, ... सभी आंतरिक गतिशील आवंटित प्रबंधित स्मृति पारदर्शी रूप से

बोलते हुए shared_ptr, एक जोखिम है: यदि संदर्भों का एक चक्र बनता है, और टूटा नहीं है, तो स्मृति रिसाव हो सकता है। यह इस स्थिति से बचने के लिए डेवलपर पर निर्भर है, सबसे सरल तरीके से बचने के लिए shared_ptrऔर दूसरा सरलतम प्रकार के स्तर पर चक्र से बचने के लिए।

नतीजतन, मेमोरी लीक सी ++ में कोई समस्या नहीं है , यहां तक ​​कि नए उपयोगकर्ताओं के लिए भी, जब तक वे उपयोग करने से बचते हैं new, deleteया std::shared_ptr। यह सी के विपरीत है जहां एक कट्टर अनुशासन आवश्यक है, और आम तौर पर अपर्याप्त है।


हालाँकि, यह उत्तर स्मृति लीक की जुड़वां-बहन का उल्लेख किए बिना पूरा नहीं होगा: झूलने वाले बिंदु

डैंगलिंग पॉइंटर (या डैंगलिंग रेफरेंस) एक पॉइंटर या रेफरेंस है जो किसी मृत वस्तु के संदर्भ में रखा जाता है। उदाहरण के लिए:

int main() {
    std::vector<int> vec;
    vec.push_back(1);     // vec: [1]

    int& a = vec.back();

    vec.pop_back();       // vec: [], "a" is now dangling

    std::cout << a << "\n";
}

लटकते हुए सूचक, या संदर्भ का उपयोग करना, अपरिभाषित व्यवहार है । सामान्य तौर पर, सौभाग्य से, यह एक तत्काल दुर्घटना है; बहुत बार, दुर्भाग्य से, यह पहले स्मृति भ्रष्टाचार का कारण बनता है ... और समय-समय पर अजीब व्यवहार फसलों के कारण होता है क्योंकि संकलक वास्तव में अजीब कोड का उत्सर्जन करता है।

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


17
पुन :: "एक लटकाने वाला सूचक, या संदर्भ का उपयोग करना, अपरिभाषित व्यवहार है । सामान्य तौर पर, सौभाग्य से, यह एक तत्काल दुर्घटना है": वास्तव में? यह मेरे अनुभव से बिल्कुल भी मेल नहीं खाता है; इसके विपरीत, मेरा अनुभव यह है कि एक झूलने वाले सूचक का उपयोग लगभग कभी भी तत्काल दुर्घटना का कारण नहीं बनता है। । ।
रुख

9
हाँ, क्योंकि "डैंगलिंग" होना एक पॉइंटर को एक बिंदु पर पहले से आवंटित मेमोरी को लक्षित करना होगा, और उस मेमोरी को आमतौर पर इस प्रक्रिया से पूरी तरह से हटाए जाने की संभावना नहीं है कि यह अब बिल्कुल भी सुलभ नहीं है, क्योंकि यह एक होगा तत्काल पुन: उपयोग के लिए अच्छा उम्मीदवार ... व्यवहार में, झूलने वाले बिंदु क्रैश का कारण नहीं बनते हैं, वे अराजकता का कारण बनते हैं।
लेउशेंको

2
"परिणाम के रूप में मेमोरी लीक सी ++ में एक मुद्दा नहीं है," निश्चित रूप से वे कर रहे हैं, वहाँ हमेशा सी पुस्तकालयों को पेंच करने के लिए बाइंडिंग है, साथ ही पुनरावर्ती शेयर्ड_प्टर्स या यहां तक ​​कि पुनरावर्ती यूनीक_एप्टर्स, और अन्य स्थितियों।
मूविंग डक

3
"नए उपयोगकर्ताओं के लिए भी C ++ में कोई समस्या नहीं है" - मैं उसे "नए उपयोगकर्ताओं के लिए अर्हता प्राप्त करूंगा जो जावा जैसी भाषा या सी से नहीं आते हैं "।
१around:

3
@leftaroundabout: यह योग्य है "जब तक वे उपयोग करने से बचते हैं new, deleteऔर shared_ptr"; बिना newऔर shared_ptrआपके पास प्रत्यक्ष स्वामित्व है इसलिए कोई लीक नहीं है। बेशक, आपके पास झूलने वाले बिंदु होने की संभावना है, आदि ... लेकिन मुझे डर है कि आपको उन से छुटकारा पाने के लिए C ++ छोड़ने की आवश्यकता है।
Matthieu M.

27

C ++ में इस चीज को RAII कहा जाता है । मूल रूप से इसका मतलब है कि कचरे को साफ किया जाता है क्योंकि आप इसे ढेर में छोड़ने के बजाय जाते हैं और क्लीनर को आपके बाद साफ करते हैं। (फुटबॉल देखने के लिए मेरे कमरे में मुझे कल्पना करो - जैसे कि मैं बीयर के डिब्बे पीता हूं और नए लोगों की जरूरत है, सी ++ रास्ता खाली कैन को फ्रिज के रास्ते पर ले जा सकता है, सी # रास्ता इसे फर्श पर चक करना है और जब वह सफाई करने आए तो नौकरानी को लेने के लिए इंतजार करें)।

अब C ++ में मेमोरी को लीक करना संभव है, लेकिन ऐसा करने के लिए आपको सामान्य निर्माणों को छोड़ना होगा और चीजों को करने के C तरीके पर वापस लौटना होगा - मेमोरी के एक ब्लॉक को आवंटित करना और उस ब्लॉक को बिना किसी भाषा की सहायता के ट्रैक रखना। कुछ लोग इस सूचक को भूल जाते हैं और इसलिए ब्लॉक को हटा नहीं सकते हैं।


9
साझा संकेत (जो RAII का उपयोग करते हैं) लीक बनाने के लिए एक आधुनिक तरीका प्रदान करते हैं। मान लीजिए कि ऑब्जेक्ट A और B एक दूसरे को साझा किए गए बिंदुओं के माध्यम से संदर्भित करते हैं, और कुछ और वस्तु ए या ऑब्जेक्ट बी को संदर्भित नहीं करते हैं। परिणाम एक रिसाव है। यह पारस्परिक संदर्भ कचरा संग्रहण वाली भाषाओं में एक गैर-मुद्दा है।
डेविड हैमेन

@David Hammen यकीन है, लेकिन यह सुनिश्चित करने के लिए लगभग हर वस्तु का पता लगाने की लागत पर। स्मार्ट पॉइंटर्स का आपका उदाहरण इस तथ्य को नजरअंदाज करता है कि स्मार्ट पॉइंटर स्वयं दायरे से बाहर हो जाएगा और फिर ऑब्जेक्ट्स को मुक्त कर दिया जाएगा। आप मानते हैं कि एक स्मार्ट पॉइंटर एक पॉइंटर की तरह होता है, इसकी नहीं, इसकी एक वस्तु जो स्टैक पर सबसे मापदंडों की तरह पास होती है। जीसी भाषाओं में होने वाली मेमोरी लीक के लिए यह बहुत अलग नहीं है,। उदाहरण के लिए, जहां एक यूआई वर्ग से एक इवेंट हैंडलर को हटाने से यह चुपचाप संदर्भित हो जाता है और इसलिए लीक होता है।
gbjbaanb

1
स्मार्ट प्वाइंट के साथ उदाहरण में @gbjbaanb, न तो स्मार्ट पॉइंटर कभी भी दायरे से बाहर हो जाता है, इसीलिए इसमें रिसाव होता है। चूंकि दोनों स्मार्ट पॉइंटर ऑब्जेक्ट्स को एक डायनेमिक स्कोप में आवंटित किया गया है , न कि एक लेक्सिकल एक के रूप में, वे प्रत्येक को विनाश से पहले एक दूसरे पर प्रतीक्षा करने का प्रयास करते हैं। तथ्य यह है कि स्मार्ट पॉइंटर्स C ++ में वास्तविक ऑब्जेक्ट हैं और न केवल पॉइंटर्स बिल्कुल वही हैं जो यहां रिसाव का कारण बनता है - स्टैक स्कोप में अतिरिक्त स्मार्ट पॉइंटर ऑब्जेक्ट्स जो कंटेनर ऑब्जेक्ट्स को भी इंगित करते हैं, जब वे खुद को नष्ट कर देते हैं, तो वे उन्हें निपटा नहीं सकते क्योंकि रिफंड है गैर शून्य।
लेउशेंको

2
.NET का तरीका इसे फर्श पर चकना नहीं है । यह सिर्फ उसे रखता है जहां यह तब तक था जब तक नौकरानी चारों ओर नहीं आती। और जिस तरह से .NET व्यवहार में मेमोरी को आवंटित करता है (संविदात्मक नहीं) के कारण, ढेर एक यादृच्छिक-एक्सेस स्टैक की तरह अधिक है। यह अनुबंधों और कागजों के ढेर होने की तरह है, और इसे एक बार गुजरने के बाद उन लोगों को छोड़ देना है जो अब मान्य नहीं हैं। और इस आसान को बनाने के लिए, जो प्रत्येक हार से बचे रहते हैं, उन्हें एक अलग स्टैक में प्रचारित किया जाता है, ताकि आप ज्यादातर समय सभी स्टैक को ट्रेस करने से बच सकें - जब तक कि पहले स्टैक काफी बड़ा नहीं हो जाता है, तब तक नौकरानी दूसरों को नहीं छूती है।
लुआण

@ Luaan यह एक सादृश्य था ... मुझे लगता है कि अगर आपने कहा कि यह मेज पर पड़े हुए डिब्बे को छोड़ देता है जब तक कि नौकरानी साफ करने के लिए नहीं आती है, तो आप खुश होंगे।
gbjbaanb

26

यह ध्यान दिया जाना चाहिए कि यह सी ++ के मामले में, एक आम गलत धारणा है कि "आपको मैनुअल मेमोरी प्रबंधन करने की आवश्यकता है"। वास्तव में, आप आमतौर पर अपने कोड में कोई स्मृति प्रबंधन नहीं करते हैं।

फिक्स्ड-साइज़ ऑब्जेक्ट्स (स्कोप आजीवन)

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

class MyObject {
    public: int x;
};

int objTest()
{
    MyObject obj;
    obj.x = 5;
    return obj.x;
}

फ़ंक्शन समाप्त होने पर स्टैक ऑब्जेक्ट स्वचालित रूप से हटा दिए जाते हैं। जावा में, वस्तुओं को हमेशा ढेर पर बनाया जाता है, और इसलिए कचरा संग्रह जैसे कुछ तंत्र द्वारा हटाया जाना चाहिए। यह स्टैक ऑब्जेक्ट्स के लिए एक गैर-समस्या है।

ऐसी वस्तुएं जो डायनेमिक डेटा का प्रबंधन करती हैं (स्कोप आजीवन)

स्टैक पर जगह का उपयोग करना एक निश्चित आकार की वस्तुओं के लिए काम करता है। जब आपको एक चर राशि की आवश्यकता होती है, जैसे कि एक सरणी, तो एक और दृष्टिकोण का उपयोग किया जाता है: सूची को एक निश्चित आकार की वस्तु में एन्क्रिप्ट किया गया है जो आपके लिए गतिशील मेमोरी का प्रबंधन करती है। यह काम करता है क्योंकि वस्तुओं में एक विशेष सफाई कार्य हो सकता है, विध्वंसक। यह कहा जाता है कि वस्तु के दायरे से बाहर जाने पर और निर्माता के विपरीत कार्य करने की गारंटी दी जाती है:

class MyList {        
public:
    // a fixed-size pointer to the actual memory.
    int* listOfInts; 
    // constructor: get memory
    MyList(size_t numElements) { listOfInts = new int[numElements]; }
    // destructor: free memory
    ~MyList() { delete[] listOfInts; }
};

int listTest()
{
    MyList list(1024);
    list.listOfInts[200] = 5;
    return list.listOfInts[200];
    // When MyList goes off stack here, its destructor is called and frees the memory.
}

जिस कोड में मेमोरी का उपयोग किया जाता है, वहां मेमोरी मैनेजमेंट बिल्कुल भी नहीं होता है। हमें केवल यह सुनिश्चित करने की आवश्यकता है कि हमारे द्वारा लिखी गई वस्तु में एक उपयुक्त विध्वंसक है। कोई फर्क नहीं पड़ता कि हम किस तरह से गुंजाइश छोड़ते हैं listTest, यह एक अपवाद के माध्यम से हो या बस इसे से लौटकर, विध्वंसक ~MyList()को बुलाया जाएगा और हमें किसी भी मेमोरी को प्रबंधित करने की आवश्यकता नहीं है।

(मुझे लगता है कि यह विध्वंसक को इंगित करने के लिए, बाइनरी नहीं ऑपरेटर का उपयोग करने के लिए एक मज़ेदार डिज़ाइन निर्णय है ~। जब संख्याओं पर उपयोग किया जाता है, तो यह बिट्स को अक्रिय करता है; अनुरूप में, यह इंगित करता है कि निर्माणकर्ता ने जो किया है वह उलटा है।)

मूल रूप से सभी C ++ ऑब्जेक्ट्स जिन्हें डायनेमिक मेमोरी की आवश्यकता होती है, इस एनकैप्सुलेशन का उपयोग करते हैं। इसे आरएआईआई कहा गया है ("संसाधन अधिग्रहण आरंभीकरण है"), जो सरल विचार को व्यक्त करने का एक अजीब तरीका है जो ऑब्जेक्ट अपनी सामग्री के बारे में परवाह करते हैं; वे जो कुछ हासिल करते हैं, वह है सफाई करना।

पॉलीमॉर्फिक ऑब्जेक्ट और आजीवन दायरे से परे

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

class MyDerivedObject : public MyObject {
    public: int y;
};
std::unique_ptr<MyObject> createObject()
{
    // actually creates an object of a derived class,
    // but the user doesn't need to know this.
    return std::make_unique<MyDerivedObject>();
}

int dynamicObjTest()
{
    std::unique_ptr<MyObject> obj = createObject();
    obj->x = 5;
    return obj->x;
    // At scope end, the unique_ptr automatically removes the object it contains,
    // calling its destructor if it has one.
}

std::shared_ptrकई ग्राहकों के बीच वस्तुओं को साझा करने के लिए एक और तरह का स्मार्ट पॉइंटर है। अंतिम क्लाइंट के दायरे से बाहर होने पर वे केवल अपनी निहित वस्तु को हटाते हैं, इसलिए उनका उपयोग उन स्थितियों में किया जा सकता है जहां यह पूरी तरह से अज्ञात है कि कितने ग्राहक होंगे और वे कब तक वस्तु का उपयोग करेंगे।

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

C ++ कोड में कहीं भी संसाधन स्वामियों के रूप में कच्चे पॉइंटर्स का उपयोग करना बेहद बुरा व्यवहार माना जाता है, बिल्डरों के बाहर कच्चे आवंटन, और deleteविध्वंसक के बाहर कच्चे कॉल, क्योंकि अपवाद होने पर उन्हें प्रबंधित करना लगभग असंभव है, और आम तौर पर सुरक्षित रूप से उपयोग करना मुश्किल है।

सबसे अच्छा: यह सभी प्रकार के संसाधनों के लिए काम करता है

RAII का एक सबसे बड़ा लाभ यह है कि यह स्मृति तक सीमित नहीं है। यह वास्तव में फाइल और सॉकेट (खोलने / बंद करने) और सिंक्रनाइज़ेशन तंत्र जैसे म्यूटेक्स (लॉकिंग / अनलॉकिंग) जैसे संसाधनों का प्रबंधन करने के लिए एक बहुत ही स्वाभाविक तरीका प्रदान करता है। मूल रूप से, प्राप्त किया जा सकने वाला प्रत्येक संसाधन C ++ में ठीक उसी तरह से प्रबंधित किया जाना चाहिए और जारी किया जाना चाहिए, और इस प्रबंधन में से कोई भी उपयोगकर्ता के लिए नहीं छोड़ा गया है। यह उन सभी वर्गों में निहित है जो निर्माणकर्ता में अधिग्रहण करते हैं और विनाशकारी में जारी करते हैं।

उदाहरण के लिए, एक म्यूटेक्स को लॉक करने वाला एक फ़ंक्शन आमतौर पर C ++ में इस तरह लिखा जाता है:

void criticalSection() {
    std::scoped_lock lock(myMutex); // scoped_lock locks the mutex
    doSynchronizedStuff();
} // myMutex is released here automatically

अन्य भाषाएं इसे और अधिक जटिल बनाती हैं, या तो आपको मैन्युअल रूप से ऐसा करने की आवश्यकता होती है (जैसे एक finallyखंड में) या वे विशेष तंत्र को स्पॉन करते हैं जो इस समस्या को हल करते हैं, लेकिन विशेष रूप से सुरुचिपूर्ण तरीके से नहीं (आमतौर पर बाद में उनके जीवन में, जब पर्याप्त लोग होते हैं कमी से पीड़ित)। इस तरह के तंत्र जावा में और C # में उपयोग स्टेटमेंट -टू-रिसोर्स हैं , दोनों ही C ++ के RAII के सन्निकटन हैं।

इसलिए, यह समझने के लिए, यह सब C ++ में RAII का एक बहुत ही सतही खाता था, लेकिन मुझे उम्मीद है कि यह पाठकों को यह समझने में मदद करता है कि C ++ में मेमोरी और यहां तक ​​कि संसाधन प्रबंधन भी "मैनुअल" नहीं है, लेकिन वास्तव में ज्यादातर स्वचालित है।


7
यह एकमात्र उत्तर है जो लोगों को गलत जानकारी नहीं देता है और न ही सी ++ को अधिक कठिन या खतरनाक से पेंट करता है।
अलेक्जेंडर रेवो

6
BTW, केवल कच्चे मालिकों को संसाधन मालिकों के रूप में उपयोग करने के लिए इसे बुरा अभ्यास माना जाता है। अगर वे कुछ ऐसा इंगित करते हैं, जो उन्हें इंगित करने की गारंटी देता है, तो इसका उपयोग करने के बारे में कुछ भी गलत नहीं है।
सिकंदर रेवो

8
मैं दूसरा सिकंदर। मैं देख रहा हूँ कि "C ++ में कोई स्वचालित मेमोरी प्रबंधन नहीं है, भूल जाओ deleteऔर तुम मर चुके हो" जवाब 30 अंकों से ऊपर रॉकेट कर रहा है और स्वीकार किया जा रहा है, जबकि यह एक पाँच है। क्या कोई वास्तव में यहाँ C ++ का उपयोग करता है?
क्वेंटिन

8

सी के संबंध में विशेष रूप से, भाषा आपको गतिशील रूप से आवंटित स्मृति का प्रबंधन करने के लिए कोई उपकरण नहीं देती है। आप यह सुनिश्चित करने के लिए पूरी तरह से जिम्मेदार हैं कि हर कहीं *allocएक संगत freeहै।

जहां चीजें वास्तव में खराब हो जाती हैं, जब संसाधन आवंटन बीच में विफल रहता है; क्या आप फिर से कोशिश करते हैं, क्या आप वापस रोल करते हैं और शुरू से शुरू करते हैं, क्या आप वापस रोल करते हैं और एक त्रुटि के साथ बाहर निकलते हैं, क्या आप बस एकमुश्त जमानत देते हैं और ओएस को इससे निपटने दें?

उदाहरण के लिए, यहाँ एक गैर-सन्निहित 2D सरणी आवंटित करने के लिए एक फ़ंक्शन है। यहाँ व्यवहार यह है कि यदि प्रक्रिया के माध्यम से एक आवंटन विफलता होती है, तो हम सब कुछ वापस रोल करते हैं और एक NULL पॉइंटर का उपयोग करके एक त्रुटि संकेत लौटाते हैं:

/**
 * Allocate space for an array of arrays; returns NULL
 * on error.
 */
int **newArr( size_t rows, size_t cols )
{
  int **arr = malloc( sizeof *arr * rows );
  size_t i;

  if ( arr ) // malloc returns NULL on failure
  {
    for ( i = 0; i < rows; i++ )
    {
      arr[i] = malloc( sizeof *arr[i] * cols );
      if ( !arr[i] )
      {
        /**
         * Whoopsie; we can't allocate any more memory for some reason.
         * We can't just return NULL at this point since we'll lose access
         * to the previously allocated memory, so we branch to some cleanup
         * code to undo the allocations made so far.  
         */
        goto cleanup;
      }
    }
  }
  goto done;

/**
 * We encountered a failure midway through memory allocation,
 * so we roll back all previous allocations and return NULL.
 */
cleanup:
  while ( i )         // this is why we didn't limit the scope of i to the for loop
    free( arr[--i] ); // delete previously allocated rows
  free( arr );        // delete arr object
  arr = NULL;

done:
  return arr;
}

यह कोड उन लोगों के साथ बट-बदसूरत हैgoto , लेकिन, किसी संरचित अपवाद हैंडलिंग तंत्र के किसी भी प्रकार की अनुपस्थिति में, यह समस्या का पूरी तरह से बिना पूरी तरह से निपटने के एकमात्र तरीका है, खासकर यदि आपके संसाधन आवंटन कोड को अधिक नेस्टेड किया गया है एक से अधिक गहरे। यह बहुत कम समय gotoमें से एक है जहां वास्तव में एक आकर्षक विकल्प है; अन्यथा आप झंडे और अतिरिक्त ifबयानों के एक समूह का उपयोग कर रहे हैं ।

आप प्रत्येक संसाधन के लिए समर्पित आबंटक / डीललोकेटर फ़ंक्शन लिखकर अपने आप पर जीवन को आसान बना सकते हैं, जैसे कुछ

Foo *newFoo( void )
{
  Foo *foo = malloc( sizeof *foo );
  if ( foo )
  {
    foo->bar = newBar();
    if ( !foo->bar ) goto cleanupBar;
    foo->bletch = newBletch(); 
    if ( !foo->bletch ) goto cleanupBletch;
    ...
  }
  goto done;

cleanupBletch:
  deleteBar( foo->bar );
  // fall through to clean up the rest

cleanupBar:
  free( foo );
  foo = NULL;

done:
  return foo;
}

void deleteFoo( Foo *f )
{
  deleteBar( f->bar );
  deleteBletch( f->bletch );
  free( f );
}

1
यह एक अच्छा जवाब है, gotoबयानों के साथ भी । यह कुछ क्षेत्रों में अभ्यास की सिफारिश की जाती है। यह सी में अपवादों के बराबर की रक्षा के लिए आमतौर पर इस्तेमाल की जाने वाली योजना है। लिनक्स कर्नेल कोड पर एक नज़र डालें, जो gotoबयानों से भरा है - और जो लीक नहीं करता है।
डेविड हैमेन

"निष्पक्षता से पूरी तरह से बाहर निकलने के बिना" -> निष्पक्षता में, यदि आप सी के बारे में बात करना चाहते हैं, तो यह शायद अच्छा अभ्यास है। C एक ऐसी भाषा है जिसका उपयोग या तो मेमोरी के ब्लॉक को संभालने के लिए किया जाता है जो कहीं और से आती है, या मेमोरी के छोटे-छोटे हिस्सों को अन्य प्रक्रियाओं से पार्सलिंग करती है, लेकिन अधिमानतः एक ही समय में दोनों को एक साथ नहीं कर रही है। यदि आप C में शास्त्रीय "ऑब्जेक्ट्स" का उपयोग कर रहे हैं, तो संभवतः आप भाषा को उसकी ताकत के लिए उपयोग नहीं कर रहे हैं।
लेहशेंको

दूसरा gotoबहिर्मुखी है। यदि आप इसे और इसे बदल goto done;गए तो यह अधिक पठनीय होगा । हालांकि अधिक जटिल मामलों में वास्तव में कई एस हो सकते हैं , जो कि तत्परता के विभिन्न स्तरों पर अनियंत्रित होने लगते हैं (सी ++ में अपवाद स्टैक अनइंडिंग द्वारा क्या किया जाएगा)। return arr;arr=NULL;done:return arr;return NULL;goto
रुस्लान

2

मैंने स्मृति समस्याओं को विभिन्न श्रेणियों में वर्गीकृत करना सीखा है।

  • एक समय टपकता है। मान लीजिए कि एक कार्यक्रम स्टार्टअप समय पर 100 बाइट्स लीक करता है, केवल फिर कभी लीक नहीं होता है। पीछा करना और उन एकमुश्त लीक को खत्म करना अच्छा है (मुझे लीक का पता लगाने की क्षमता से साफ रिपोर्ट रखना पसंद है) लेकिन यह जरूरी नहीं है। कभी-कभी बड़ी समस्याएं होती हैं जिन पर हमला करने की आवश्यकता होती है।

  • बार-बार लीक। एक फ़ंक्शन जिसे कार्यक्रमों के दौरान पुनरावृत्ति कहा जाता है, जो जीवन भर नियमित रूप से स्मृति को एक बड़ी समस्या बना देता है। ये ड्रिप्स प्रोग्राम को और संभवतः ओएस को मौत के लिए प्रताड़ित करेंगे।

  • परस्पर संदर्भ। यदि ऑब्जेक्ट ए और बी साझा बिंदुओं के माध्यम से एक दूसरे का संदर्भ देते हैं, तो आपको कुछ विशेष करना होगा, या तो उन वर्गों के डिजाइन में या कोड में जो उन वर्गों को परिपत्रता को तोड़ने के लिए लागू / उपयोग करता है। (यह कचरा एकत्र करने वाली भाषाओं के लिए कोई समस्या नहीं है।)

  • बहुत ज्यादा याद करना। यह कचरा / मेमोरी लीक का दुष्ट चचेरा भाई है। RAII यहां मदद नहीं करेगा, न ही कचरा संग्रह करेगा। यह किसी भी भाषा में एक समस्या है। यदि कुछ सक्रिय चर में एक मार्ग है जो इसे स्मृति के कुछ यादृच्छिक भाग से जोड़ता है, तो स्मृति का यादृच्छिक हिस्सा कचरा नहीं है। एक कार्यक्रम बनाना विस्मृत हो जाता है इसलिए यह कई दिनों तक चल सकता है। एक प्रोग्राम बनाना जो कई महीनों तक चल सकता है (जैसे, जब तक डिस्क विफल नहीं होता है) बहुत, बहुत मुश्किल है।

मुझे एक लंबे, लंबे समय के लिए लीक के साथ एक गंभीर समस्या नहीं थी। C ++ में RAII का उपयोग करने से उन ड्रिप और लीक को संबोधित करने में बहुत मदद मिलती है। (हालांकि एक साझा साझेदारों के साथ सावधान रहना पड़ता है।) बहुत अधिक महत्वपूर्ण बात यह है कि मुझे उन अनुप्रयोगों के साथ समस्याएँ हैं जिनकी स्मृति का उपयोग निरंतर बढ़ता जा रहा है और स्मृति के लिए अनदेखी कनेक्शन के कारण बढ़ता जा रहा है जो अब किसी काम का नहीं है।


-6

यह C ++ प्रोग्रामर पर निर्भर है कि वह अपने स्वयं के कचरा संग्रह के फॉर्म को लागू करने के लिए आवश्यक है। ऐसा करने में विफलता के परिणामस्वरूप 'मेमोरी लीक' कहा जाता है। कचरा संग्रह में निर्मित 'उच्च स्तर' भाषाओं (जैसे जावा) के लिए यह बहुत आम है, लेकिन सी और सी ++ जैसी 'निम्न स्तर' भाषाएं नहीं हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.