C ++ में उचित स्टैक और हीप उपयोग?


122

मैं थोड़ी देर के लिए प्रोग्रामिंग कर रहा हूं लेकिन यह ज्यादातर जावा और सी # है। मैं वास्तव में अपने दम पर स्मृति का प्रबंधन नहीं किया है। मैंने हाल ही में C ++ में प्रोग्रामिंग शुरू की और मैं थोड़ा उलझन में हूं कि कब मुझे स्टैक पर चीजों को स्टोर करना चाहिए और कब उन्हें ढेर पर स्टोर करना चाहिए।

मेरी समझ यह है कि चर जो बहुत बार एक्सेस किए जाते हैं उन्हें स्टैक और ऑब्जेक्ट्स पर संग्रहित किया जाना चाहिए, शायद ही कभी उपयोग किए जाने वाले वेरिएबल और बड़े डेटा स्ट्रक्चर सभी को ढेर पर संग्रहीत किया जाना चाहिए। क्या यह सही है या मैं गलत हूं?


जवाबों:


242

नहीं, स्टैक और हीप के बीच का अंतर प्रदर्शन नहीं है। यह जीवनकाल है: एक फ़ंक्शन के अंदर कोई भी स्थानीय चर (कुछ भी जिसे आप मालॉक नहीं करते हैं) (या नया) स्टैक पर रहता है। जब आप फंक्शन से लौटते हैं तो यह चला जाता है। यदि आप ऐसा कुछ चाहते हैं जो उस फ़ंक्शन से अधिक समय तक जीवित रहे, तो आपको उसे ढेर पर आवंटित करना होगा।

class Thingy;

Thingy* foo( ) 
{
  int a; // this int lives on the stack
  Thingy B; // this thingy lives on the stack and will be deleted when we return from foo
  Thingy *pointerToB = &B; // this points to an address on the stack
  Thingy *pointerToC = new Thingy(); // this makes a Thingy on the heap.
                                     // pointerToC contains its address.

  // this is safe: C lives on the heap and outlives foo().
  // Whoever you pass this to must remember to delete it!
  return pointerToC;

  // this is NOT SAFE: B lives on the stack and will be deleted when foo() returns. 
  // whoever uses this returned pointer will probably cause a crash!
  return pointerToB;
}

स्टैक क्या है इसकी स्पष्ट समझ के लिए, दूसरे छोर से इस पर आएं - यह समझने की कोशिश करें कि स्टैक उच्च स्तरीय भाषा के संदर्भ में क्या करता है, "कॉल स्टैक" और "कॉलिंग कन्वेंशन" देखें और देखें कि क्या है मशीन वास्तव में तब करती है जब आप किसी फ़ंक्शन को कॉल करते हैं। कंप्यूटर मेमोरी केवल पतों की एक श्रृंखला है; "हीप" और "स्टैक" संकलक के आविष्कार हैं।


7
यह जोड़ना सुरक्षित होगा कि आम तौर पर आकार की जानकारी ढेर पर जाती है। एकमात्र अपवाद जो मुझे पता है कि वीएलए के सी 99 (जिसमें सीमित समर्थन है) और एलोका () फ़ंक्शन हैं जो अक्सर सी प्रोग्रामर द्वारा गलत समझा जाता है।
डैन ओल्सन

10
अच्छी तरह से स्पष्टीकरण, हालांकि अक्सर आवंटन और / या सौदेबाजी के साथ एक बहुपरत परिदृश्य में, ढेर विवाद का एक बिंदु है, इस प्रकार प्रदर्शन को प्रभावित करता है। फिर भी, स्कोप लगभग हमेशा निर्णायक कारक होता है।
पीटरचेन

18
ज़रूर, और नया / मालॉक () अपने आप में एक धीमा ऑपरेशन है, और स्टैक एक मनमाना हथियार लाइन की तुलना में dcache में होने की अधिक संभावना है। ये वास्तविक विचार हैं, लेकिन आमतौर पर जीवनकाल के सवाल के लिए माध्यमिक हैं।
क्रैशवर्क्स

1
क्या यह सच है "कंप्यूटर मेमोरी केवल पते की एक श्रृंखला है?" ढेर "और" स्टैक "संकलन के आविष्कार हैं" ?? मैंने कई स्थानों पर पढ़ा है कि स्टैक हमारे कंप्यूटर की मेमोरी का एक विशेष क्षेत्र है।
विनीत चित्ती

2
@kai यह कल्पना करने का एक तरीका है, लेकिन जरूरी नहीं कि शारीरिक रूप से सच हो। ओएस एक आवेदन के ढेर और ढेर को आवंटित करने के लिए जिम्मेदार है। संकलक भी जिम्मेदार है, लेकिन मुख्य रूप से यह ऐसा करने के लिए ओएस पर निर्भर करता है। ढेर सीमित है, और ढेर नहीं है। यह जिस तरह से ओएस इन मेमोरी एड्रेस को छाँटकर कुछ और अधिक संरचित करने के तरीके के कारण होता है ताकि एक ही सिस्टम पर कई एप्लिकेशन चल सकें। ढेर और ढेर केवल एक ही नहीं हैं, लेकिन वे आम तौर पर केवल दो हैं जिनके बारे में ज्यादातर डेवलपर्स चिंतित हैं।
tsturzl

42

में कहना चाहूंगा:

यदि आप कर सकते हैं, तो इसे स्टैक पर स्टोर करें।

अगर आप की जरूरत है, तो इसे ढेर पर स्टोर करें।

इसलिए, ढेर को प्राथमिकता दें। कुछ संभावित कारण जो आप स्टैक पर कुछ स्टोर नहीं कर सकते हैं:

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

यह संभव है, समझदार संकलक के साथ, ढेर पर गैर-निश्चित आकार की वस्तुओं को आवंटित करने के लिए (आमतौर पर सरणियों जिसका आकार संकलन समय पर ज्ञात नहीं है)।


1
केबी के एक जोड़े से अधिक कुछ भी आमतौर पर ढेर पर लगाया जाता है। मैं बारीकियों को नहीं जानता, लेकिन मुझे याद नहीं है कि "स्टैक के साथ काम करना" "कुछ हील्स" था।
दान ओल्सन

2
यह एक ऐसी चीज है जिसकी शुरुआत में मुझे किसी उपयोगकर्ता की चिंता नहीं होगी। उपयोगकर्ता के लिए, वैक्टर और सूची स्टैक पर आवंटित किए गए प्रतीत होते हैं, भले ही ths STL सामग्री को ढेर पर संग्रहीत करता है। स्पष्ट रूप से नए / हटाए जाने पर निर्णय लेने की रेखा पर प्रश्न अधिक लगता था।
डेविड रॉड्रिग्ज - drieas

1
Dan: मैंने 32 g linux के नीचे स्टैक पर 2 gigs (Yes, G as in GIGS) डाल दिया है। स्टैक सीमाएँ OS पर निर्भर हैं।
श्री

6
mrree: निनटेंडो डीएस स्टैक 16 किलोबाइट है। कुछ स्टैक सीमाएँ हार्डवेयर निर्भर हैं।
चींटी

चींटी: सभी ढेर हार्डवेयर निर्भर, ओएस पर निर्भर हैं, और कंपाइलर निर्भर भी हैं।
वेलियामी

24

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

std::vector<int> v(10);

एक फ़ंक्शन के शरीर में, जो vectorस्टैक पर दस पूर्णांक के (गतिशील सरणी) की घोषणा करता है । लेकिन संग्रहण द्वारा प्रबंधित संग्रहण vectorस्टैक पर नहीं है।

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

ऐसा नहीं। मान लीजिए कि समारोह था:

void GetSomeNumbers(std::vector<int> &result)
{
    std::vector<int> v(10);

    // fill v with numbers

    result.swap(v);
}

तो एक swapफ़ंक्शन के साथ कुछ भी (और किसी भी जटिल मूल्य प्रकार के पास एक होना चाहिए) कुछ हीप डेटा के लिए एक तरह के विद्रोही संदर्भ के रूप में काम कर सकता है, एक प्रणाली के तहत जो उस डेटा के एकल स्वामी की गारंटी देता है।

इसलिए आधुनिक सी ++ दृष्टिकोण नग्न स्थानीय सूचक चर में ढेर डेटा के पते को कभी भी संग्रहीत नहीं करना है । सभी ढेर आवंटन कक्षाओं के अंदर छिपे होने चाहिए।

यदि आप ऐसा करते हैं, तो आप अपने कार्यक्रम के सभी चर के बारे में सोच सकते हैं जैसे कि वे सरल मूल्य प्रकार थे, और पूरी तरह से हीप के बारे में भूल जाते हैं (कुछ हीप डेटा के लिए एक नया मूल्य-जैसे आवरण वर्ग लिखने के अलावा, जो असामान्य होना चाहिए) ।

आपको अनुकूलन करने में मदद करने के लिए केवल एक विशेष ज्ञान को बनाए रखना होगा: जहां संभव हो, इसके बजाय एक चर को दूसरे को सौंपने के बजाय:

a = b;

उन्हें इस तरह स्वैप करें:

a.swap(b);

क्योंकि यह बहुत तेज़ है और यह अपवाद नहीं फेंकता है। केवल आवश्यकता यह है कि आपको bउसी मूल्य को जारी रखने की आवश्यकता नहीं है (यह aबदले में मूल्य प्राप्त करने जा रहा है , जो कि ट्रैश हो जाएगा a = b)।

नकारात्मक पक्ष यह है कि यह दृष्टिकोण आपको वास्तविक रिटर्न मान के बजाय आउटपुट मापदंडों के माध्यम से कार्यों से मान वापस करने के लिए मजबूर करता है। लेकिन वे इसे C ++ 0x में रैवल्यू रेफरेंस के साथ ठीक कर रहे हैं ।

सभी की सबसे जटिल स्थितियों में, आप इस विचार को सामान्य चरम पर ले जाएंगे और एक स्मार्ट पॉइंटर क्लास का उपयोग करेंगे जैसे कि shared_ptrपहले से ही tr1 में है। (हालांकि मैं तर्क दूंगा कि यदि आपको इसकी आवश्यकता प्रतीत होती है, तो आप संभवतः मानक C ++ की प्रयोज्यता के मधुर स्थान से बाहर चले गए हैं।)


6

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


5

अन्य उत्तरों में जोड़ने के लिए, यह प्रदर्शन के बारे में भी हो सकता है, कम से कम थोड़ा सा। ऐसा नहीं है कि आपको इस बारे में चिंता करनी चाहिए जब तक कि यह आपके लिए प्रासंगिक न हो, लेकिन:

ढेर में आबंटन के लिए एक ट्रैकिंग को मेमोरी का एक ब्लॉक खोजने की आवश्यकता होती है, जो एक निरंतर-समय ऑपरेशन नहीं है (और कुछ चक्र और ओवरहेड लेता है)। यह धीमा हो सकता है क्योंकि स्मृति खंडित हो जाती है, और / या आप अपने पते के स्थान का 100% उपयोग करने के करीब पहुंच रहे हैं। दूसरी ओर, स्टैक आवंटन निरंतर-समय होते हैं, मूल रूप से "मुक्त" संचालन।

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


हीप और स्टैक दोनों वर्चुअल मेमोरी हैं। नई खोज में मैप करने में जो लगता है, उसकी तुलना में ढेर खोज समय बहुत तेज़ी से होता है। 32 बिट लिनक्स के तहत, मैं अपने स्टैक पर 2gig डाल सकता हूं। एमएसीएस के तहत, मुझे लगता है कि स्टैक 65Meg तक सीमित है।
श्री

3

ढेर अधिक कुशल है, और प्रबंधित स्कूप डेटा के लिए आसान है।

लेकिन ढेर का उपयोग कुछ केबी से बड़े के लिए किया जाना चाहिए (यह सी ++ में आसान है, बस boost::scoped_ptrआवंटित मेमोरी को एक पॉइंटर रखने के लिए स्टैक पर बनाएं )।

एक पुनरावर्ती एल्गोरिदम पर विचार करें जो अपने आप में कॉल करता रहता है। कुल स्टैक उपयोग को सीमित करना या अनुमान लगाना बहुत कठिन है! जबकि ढेर पर, आबंटक ( malloc()या new) वापस NULLया throwआईएनजी द्वारा स्मृति का संकेत दे सकता है ।

स्रोत : लिनक्स कर्नेल जिसका स्टैक 8KB से बड़ा नहीं है!


अन्य पाठकों के संदर्भ के लिए: (ए) "यहां" विशुद्ध रूप से उपयोगकर्ता की व्यक्तिगत राय है, जो सबसे अच्छा 1 उद्धरण और 1 परिदृश्य से लिया गया है, जिससे कई उपयोगकर्ता मुठभेड़ (पुनरावृत्ति) की संभावना नहीं है। इसके अलावा, (बी) मानक पुस्तकालय प्रदान करता है std::unique_ptr, जिसे किसी भी बाहरी पुस्तकालय जैसे कि बूस्ट के लिए पसंद किया जाना चाहिए (हालांकि यह मानक समय पर चीजों को खिलाता है)।
अंडरस्कोर_ड


1

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


4
मुझे संदेह है कि वह पूछ रहा था कि कब ढेर पर चीजें डालनी हैं, कैसे नहीं।
स्टीव रोव

0

मेरी राय में दो निर्णायक कारक हैं

1) Scope of variable
2) Performance.

मैं ज्यादातर मामलों में स्टैक का उपयोग करना पसंद करूंगा लेकिन अगर आपको बाहर के दायरे में वेरिएबल तक पहुंच की आवश्यकता है तो आप ढेर का उपयोग कर सकते हैं।

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


0

शायद यह बहुत अच्छी तरह से जवाब दिया गया है। मैं आपको निम्न स्तर के विवरण की गहरी समझ रखने के लिए लेखों की श्रृंखला के नीचे इंगित करना चाहूंगा। एलेक्स डर्बी के पास लेखों की एक श्रृंखला है, जहां वह आपको डिबगर के माध्यम से चलता है। यहाँ स्टैक के बारे में भाग 3 है। http://www.altdevblogaday.com/2011/12/14/cc-low-level-curriculum-part-3-the-stack/


यह लिंक मृत प्रतीत होता है, लेकिन इंटरनेट आर्काइव वेबैक मशीन की जाँच यह इंगित करती है कि यह केवल स्टैक के बारे में बात करती है और इसलिए स्टैक बनाम हीप के विशिष्ट प्रश्न का उत्तर देने के लिए कुछ भी नहीं करती है । -1
अंडरस्कोर_ड
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.