सी malloc()
में, ढेर में मेमोरी का एक क्षेत्र आवंटित करता है और इसके लिए एक संकेतक लौटाता है। बस इतना ही। मेमोरी असिंचित है और आपको इसकी कोई गारंटी नहीं है कि यह सभी शून्य या कुछ और है।
जावा में, कॉलिंग new
एक ढेर आधारित आवंटन की तरह ही करता है malloc()
, लेकिन आपको एक टन अतिरिक्त सुविधा भी मिलती है (या यदि आप चाहें तो ओवरहेड)। उदाहरण के लिए, आपको आवंटित किए जाने वाले बाइट्स की संख्या को स्पष्ट रूप से निर्दिष्ट करने की आवश्यकता नहीं है। संकलक आपके द्वारा आवंटित की जाने वाली वस्तु के प्रकार के आधार पर आपके लिए यह पता लगाता है। इसके अतिरिक्त, ऑब्जेक्ट कंस्ट्रक्टर को कहा जाता है (जो कि आप तर्क दे सकते हैं कि क्या आप नियंत्रित करना चाहते हैं कि कैसे इनिशियलाइज़ेशन होता है)। जब new
रिटर्न मिलता है, तो आपको गारंटी दी जाती है कि वह एक ऑब्जेक्ट है जिसे इनिशियलाइज़ किया गया है।
लेकिन हां, कॉल के अंत में दोनों का परिणाम होता है malloc()
और new
बस ढेर-आधारित डेटा के कुछ हिस्से को इंगित करता है।
आपके प्रश्न का दूसरा भाग स्टैक और ढेर के बीच के अंतर के बारे में पूछता है। संकलक डिजाइन पर एक कोर्स लेने (या एक किताब के बारे में पढ़ने) द्वारा अधिक व्यापक उत्तर पाए जा सकते हैं। ऑपरेटिंग सिस्टम पर एक कोर्स भी मददगार होगा। ढेर और ढेर के बारे में भी एसओ पर कई सवाल और जवाब हैं।
ऐसा कहने के बाद, मैं एक सामान्य अवलोकन देता हूँ, मुझे आशा है कि बहुत अधिक क्रिया नहीं है और इसका उद्देश्य काफी उच्च स्तर पर अंतर को स्पष्ट करना है।
मौलिक रूप से, दो मेमोरी मैनेजमेंट सिस्टम, यानी ढेर और ढेर होने का मुख्य कारण दक्षता के लिए है । एक माध्यमिक कारण यह है कि प्रत्येक दूसरे की तुलना में कुछ प्रकार की समस्याओं में बेहतर है।
स्टैक मुझे अवधारणा के रूप में समझने के लिए कुछ आसान हैं, इसलिए मैं स्टैक्स के साथ शुरू करता हूं। आइए इस समारोह में सी पर विचार करें ...
int add(int lhs, int rhs) {
int result = lhs + rhs;
return result;
}
ऊपर काफी सीधा सा लगता है। हम नामित फ़ंक्शन को परिभाषित करते हैं add()
और बाएं और दाएं परिशिष्ट में पास होते हैं। फ़ंक्शन उन्हें जोड़ता है और एक परिणाम देता है। कृपया इस तरह के ओवरफ्लो जैसे सभी एज-केस सामानों को अनदेखा करें, इस समय यह चर्चा के लिए नहीं है।
add()
समारोह का उद्देश्य बिल्कुल स्पष्ट लगता है, लेकिन हम अपने जीवन चक्र के बारे में क्या बता सकते हैं? विशेष रूप से इसकी स्मृति उपयोग की आवश्यकता है?
सबसे महत्वपूर्ण बात यह है कि संकलक एक प्राथमिकताओं को जानता है (यानी संकलन समय पर) डेटा प्रकार कितने बड़े हैं और कितने का उपयोग किया जाएगा। lhs
और rhs
तर्क हैं sizeof(int)
, 4 प्रत्येक बाइट्स। चर result
भी है sizeof(int)
। संकलक बता सकता है कि add()
फ़ंक्शन 4 bytes * 3 ints
मेमोरी का उपयोग करता है या कुल 12 बाइट्स का है।
जब add()
फ़ंक्शन को कॉल किया जाता है, तो स्टैक पॉइंटर नामक एक हार्डवेयर रजिस्टर इसमें एक पता होगा जो स्टैक के शीर्ष पर इंगित करता है। add()
फ़ंक्शन को चलाने के लिए आवश्यक मेमोरी को आवंटित करने के लिए, सभी फ़ंक्शन-एंट्री कोड को करने की आवश्यकता है एक स्टैक पॉइंटर रजिस्टर मान को 12 से घटाकर एक एकल विधानसभा भाषा निर्देश जारी करना है। ऐसा करने में, यह तीन के लिए स्टैक पर स्टोरेज बनाता है। ints
, एक-एक के लिए lhs
, rhs
, और result
। एक निर्देश को निष्पादित करके आपको आवश्यक मेमोरी स्थान प्राप्त करना गति के मामले में एक बड़ी जीत है क्योंकि एकल निर्देश एक घड़ी की टिक में निष्पादित करते हैं (एक दूसरे 1 गीगाहर्ट्ज सीपीयू का 1 बिलियन)।
इसके अलावा, संकलक के दृष्टिकोण से, यह उन चर के लिए एक मानचित्र बना सकता है जो एक सरणी को अनुक्रमित करने जैसा एक बहुत भयानक दिखता है:
lhs: ((int *)stack_pointer_register)[0]
rhs: ((int *)stack_pointer_register)[1]
result: ((int *)stack_pointer_register)[2]
फिर, यह सब बहुत तेज है।
जब add()
फ़ंक्शन बाहर निकलता है तो उसे साफ करना पड़ता है। यह स्टैक पॉइंटर रजिस्टर से 12 बाइट्स घटाकर ऐसा करता है। यह एक कॉल के समान है, free()
लेकिन यह केवल एक सीपीयू निर्देश का उपयोग करता है और यह केवल एक टिक लेता है। यह बहुत, बहुत तेज है।
अब एक ढेर-आधारित आवंटन पर विचार करें। यह तब होता है जब हम एक प्राथमिकता को नहीं जानते हैं कि हमें कितनी मेमोरी की आवश्यकता है (यानी हम केवल इसके बारे में रनटाइम में सीखेंगे)।
इस समारोह पर विचार करें:
int addRandom(int count) {
int numberOfBytesToAllocate = sizeof(int) * count;
int *array = malloc(numberOfBytesToAllocate);
int result = 0;
if array != NULL {
for (i = 0; i < count; ++i) {
array[i] = (int) random();
result += array[i];
}
free(array);
}
return result;
}
ध्यान दें कि addRandom()
फ़ंक्शन को संकलन समय पर पता नहीं है कि count
तर्क का मूल्य क्या होगा। इस वजह से, यह परिभाषित करने की कोशिश करने का कोई मतलब नहीं है array
कि अगर हम इसे स्टैक पर रख रहे हैं, तो इस तरह से:
int array[count];
यदि count
बहुत बड़ा है तो यह हमारे ढेर को बहुत बड़ा कर सकता है और अन्य कार्यक्रम खंडों को अधिलेखित कर सकता है। जब यह स्टैक ओवरफ्लो होता है तो आपका प्रोग्राम क्रैश (या इससे भी बदतर) हो जाता है।
इसलिए, ऐसे मामलों में जब हम नहीं जानते कि रनटाइम तक हमें कितनी मेमोरी की आवश्यकता होगी, हम उपयोग करते हैं malloc()
। फिर हम बस ज़रूरत पड़ने पर बाइट्स की संख्या पूछ सकते हैं, और malloc()
यह जाँचेंगे कि क्या यह कई बाइट्स को रोक सकता है। यदि यह महान हो सकता है, तो हम इसे वापस ले लेते हैं, यदि नहीं, तो हमें एक NULL पॉइंटर मिलता है जो हमें कॉल को malloc()
विफल बताता है । विशेष रूप से हालांकि, कार्यक्रम दुर्घटना नहीं करता है! बेशक आप प्रोग्रामर के रूप में यह तय कर सकते हैं कि संसाधन आवंटन विफल होने पर आपके प्रोग्राम को चलाने की अनुमति नहीं है, लेकिन प्रोग्रामर द्वारा शुरू की गई समाप्ति एक दुर्घटना से अलग है।
इसलिए अब हमें दक्षता देखने के लिए वापस आना होगा। स्टैक एलोकेटर सुपर फास्ट है - एक निर्देश आवंटित करने के लिए, एक निर्देश डील करने के लिए, और यह कंपाइलर द्वारा किया जाता है, लेकिन याद रखें स्टैक का मतलब किसी ज्ञात आकार के स्थानीय चर जैसी चीजों के लिए होता है, इसलिए यह काफी छोटा हो जाता है।
दूसरी ओर ढेर आवंटनकर्ता परिमाण धीमी के कई आदेश हैं। यह देखने के लिए तालिकाओं में एक लुकअप करना है कि क्या यह पर्याप्त मुक्त मेमोरी है जो उपयोगकर्ता चाहता है कि स्मृति की मात्रा का प्रतिरोध करने में सक्षम हो। यह उन तालिकाओं को अद्यतन करने के बाद है जब यह सुनिश्चित करता है कि स्मृति कोई और उस ब्लॉक का उपयोग नहीं कर सकती (इस बहीखाता पद्धति को आवंटनकर्ता को अपने लिए मेमोरी को आरक्षित करने की आवश्यकता हो सकती है, इसके अलावा जो इसे वीजेंट करने की योजना है)। आवंटनकर्ता को यह सुनिश्चित करने के लिए लॉकिंग रणनीतियों को नियोजित करना होगा कि यह थ्रेड-सुरक्षित तरीके से मेमोरी को वेंड करता है। और जब स्मृति आखिर हैfree()
डी, जो अलग-अलग समय पर होता है और आम तौर पर कोई अनुमान लगाने योग्य क्रम में नहीं होता है, आवंटनकर्ता को ढेर के खंडों की मरम्मत करने के लिए सन्निहित ब्लॉकों को ढूंढना और उन्हें एक साथ सिलाई करना होता है। अगर ऐसा लगता है कि यह सब कुछ पूरा करने के लिए एक एकल सीपीयू निर्देश से अधिक लेने जा रहा है, तो आप सही हैं! यह बहुत जटिल है और इसमें कुछ समय लगता है।
लेकिन ढेर बड़े हैं। ढेर से भी बड़ा। हम उनसे बहुत सी मेमोरी प्राप्त कर सकते हैं और वे महान हैं जब हम संकलन समय पर नहीं जानते हैं कि हमें कितनी मेमोरी की आवश्यकता होगी। इसलिए हम एक प्रबंधित मेमोरी सिस्टम के लिए गति को व्यापार करते हैं जो दुर्घटनाग्रस्त होने के बजाय विनम्रता से हमें बताती है जब हम कुछ बड़ा आवंटित करने का प्रयास करते हैं।
मुझे उम्मीद है कि आपके कुछ सवालों के जवाब देने में मदद मिलेगी। कृपया मुझे बताएं कि क्या आप उपरोक्त में से किसी पर स्पष्टीकरण चाहते हैं।