क्या जावा में ऑब्जेक्ट इनिशियलाइज़ेशन "Foo f = new Foo ()" अनिवार्य रूप से C में पॉइंटर के लिए मॉलॉक का उपयोग करने के समान है?


9

मैं जावा में ऑब्जेक्ट क्रिएशन के पीछे की वास्तविक प्रक्रिया को समझने की कोशिश कर रहा हूं - और मैं अन्य प्रोग्रामिंग भाषाओं को मानता हूं।

क्या यह मान लेना गलत होगा कि जावा में ऑब्जेक्ट इनिशियलाइज़ेशन वैसा ही है, जब आप सी में स्ट्रक्चर के लिए मॉलोक का इस्तेमाल करते हैं?

उदाहरण:

Foo f = new Foo(10);
typedef struct foo Foo;
Foo *f = malloc(sizeof(Foo));

क्या इसीलिए वस्तुओं को ढेर के बजाय ढेर पर कहा जाता है? क्योंकि वे अनिवार्य रूप से डेटा के लिए केवल संकेत हैं?


वस्तुओं को प्रबंधित भाषाओं के लिए c # / java जैसे ढेर पर बनाया गया है। सीपीपी में आप स्टैक पर ऑब्जेक्ट्स बना सकते हैं
बेस

जावा / सी # के निर्माताओं ने विशेष रूप से ढेर पर वस्तुओं को स्टोर करने का निर्णय क्यों लिया?
जूल्स

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

@ जावा में ऑब्जेक्ट्स अभी भी रन-टाइम पर "विघटित" हो सकते हैं (जिन्हें scalar-replacement) केवल सादे क्षेत्रों में कहा जाता है जो केवल स्टैक पर रहते हैं; लेकिन यह कुछ ऐसा है जो JITनहीं करता है javac
यूजीन

"हीप" आवंटित वस्तुओं / स्मृति से जुड़े गुणों के एक समूह का सिर्फ एक नाम है। C / C ++ में आप संपत्तियों के दो अलग-अलग सेटों में से चुन सकते हैं, जिन्हें "स्टैक" और "हीप" कहा जाता है, सी # और जावा में, सभी ऑब्जेक्ट आवंटन में एक ही निर्दिष्ट व्यवहार होता है, जो "हीप" नाम के तहत जाता है, जो नहीं करता है इसका मतलब है कि ये गुण C / C ++ "हीप" के लिए समान हैं, वास्तव में, वे नहीं हैं। इसका मतलब यह नहीं है कि वस्तुओं के प्रबंधन के लिए कार्यान्वयन में अलग-अलग रणनीतियाँ नहीं हो सकती हैं, इसका मतलब है कि वे रणनीतियाँ अनुप्रयोग तर्क के लिए अप्रासंगिक हैं।
होल्गर

जवाबों:


5

सी 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()डी, जो अलग-अलग समय पर होता है और आम तौर पर कोई अनुमान लगाने योग्य क्रम में नहीं होता है, आवंटनकर्ता को ढेर के खंडों की मरम्मत करने के लिए सन्निहित ब्लॉकों को ढूंढना और उन्हें एक साथ सिलाई करना होता है। अगर ऐसा लगता है कि यह सब कुछ पूरा करने के लिए एक एकल सीपीयू निर्देश से अधिक लेने जा रहा है, तो आप सही हैं! यह बहुत जटिल है और इसमें कुछ समय लगता है।

लेकिन ढेर बड़े हैं। ढेर से भी बड़ा। हम उनसे बहुत सी मेमोरी प्राप्त कर सकते हैं और वे महान हैं जब हम संकलन समय पर नहीं जानते हैं कि हमें कितनी मेमोरी की आवश्यकता होगी। इसलिए हम एक प्रबंधित मेमोरी सिस्टम के लिए गति को व्यापार करते हैं जो दुर्घटनाग्रस्त होने के बजाय विनम्रता से हमें बताती है जब हम कुछ बड़ा आवंटित करने का प्रयास करते हैं।

मुझे उम्मीद है कि आपके कुछ सवालों के जवाब देने में मदद मिलेगी। कृपया मुझे बताएं कि क्या आप उपरोक्त में से किसी पर स्पष्टीकरण चाहते हैं।


int64-बिट प्लेटफॉर्म पर 8 बाइट्स नहीं है। यह अभी भी 4. इसके साथ ही, कंपाइलर को intस्टैक में से तीसरे को रिटर्न रजिस्टर में ऑप्टिमाइज़ करने की बहुत संभावना है । वास्तव में, दोनों तर्क किसी भी 64-बिट प्लेटफ़ॉर्म पर रजिस्टरों में होने की संभावना है।
एसएस ऐनी

मैंने int64-बिट प्लेटफ़ॉर्म पर 8-बाइट के विवरण को हटाने के लिए अपना उत्तर संपादित किया है। आप सही हैं जो intजावा में 4-बाइट्स बना हुआ है। मैंने अपने उत्तर के शेष भाग को छोड़ दिया है, क्योंकि मेरा मानना ​​है कि कंपाइलर ऑप्टिमाइज़ेशन घोड़े के सामने गाड़ी डालता है। हां, आप इन बिंदुओं पर भी सही हैं, लेकिन सवाल ढेर बनाम ढेर पर स्पष्टीकरण मांगता है। आरवीओ, रजिस्टरों से गुजरने वाले तर्क, कोड एलीगेशन, आदि मूल अवधारणाओं को पछाड़ते हैं और मूल सिद्धांतों को समझने के तरीके में शामिल हो जाते हैं।
बराबर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.