एक संरचना पर "नया" का उपयोग करने से यह ढेर या ढेर पर आवंटित होता है?


290

जब आप newऑपरेटर के साथ एक वर्ग का एक उदाहरण बनाते हैं , तो मेमोरी को ढेर पर आवंटित किया जाता है। जब आप newऑपरेटर के साथ एक संरचना का एक उदाहरण बनाते हैं, जहां मेमोरी आवंटित की जाती है, तो ढेर पर या स्टैक पर?

जवाबों:


306

ठीक है, चलो देखते हैं कि क्या मैं इसे किसी भी स्पष्ट कर सकता हूं।

सबसे पहले, ऐश सही है: सवाल यह नहीं है कि मूल्य प्रकार चर कहां आवंटित किए जाते हैं। यह एक अलग सवाल है - और एक जिसका जवाब सिर्फ "स्टैक पर" नहीं है। यह उससे अधिक जटिल है (और C # 2 द्वारा और भी अधिक जटिल बना दिया गया है)। मेरे पास विषय पर एक लेख है और यदि अनुरोध किया गया है, तो उस पर विस्तार करेंगे, लेकिन चलो बस newऑपरेटर के साथ व्यवहार करें ।

दूसरे, यह सब वास्तव में इस बात पर निर्भर करता है कि आप किस स्तर की बात कर रहे हैं। मैं देख रहा हूँ कि संकलक स्रोत कोड के साथ क्या करता है, IL के संदर्भ में यह बनाता है। यह अधिक से अधिक संभव है कि जेआईटी संकलक "तार्किक" आवंटन के काफी दूर के अनुकूलन के संदर्भ में चतुर चीजें करेगा।

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

अंत में, यह सब सिर्फ वर्तमान कार्यान्वयन के साथ है। C # कल्पना इसमें बहुत कुछ निर्दिष्ट नहीं करती है - यह प्रभावी रूप से कार्यान्वयन विवरण है। ऐसे लोग हैं जो मानते हैं कि प्रबंधित कोड डेवलपर्स को वास्तव में परवाह नहीं करनी चाहिए। मुझे यकीन नहीं है कि मैं इतनी दूर जाऊंगा, लेकिन यह एक ऐसी दुनिया की कल्पना करने के लायक है जहां वास्तव में सभी स्थानीय चर ढेर पर रहते हैं - जो अभी भी कल्पना के अनुरूप होगा।


newमूल्य प्रकारों पर ऑपरेटर के साथ दो अलग-अलग स्थितियां हैं: आप या तो एक पैरामीटर रहित निर्माता (जैसे new Guid()) या एक पैरामीटर वाले निर्माता (जैसे new Guid(someString)) कह सकते हैं । ये काफी अलग आईएल उत्पन्न करते हैं। यह समझने के लिए कि आपको C # और CLI स्पेक्स की तुलना करने की आवश्यकता क्यों है: C # के अनुसार, सभी मान प्रकारों में एक पैरामीटर रहित कंस्ट्रक्टर होता है। सीएलआई कल्पना के अनुसार, कोई भी मूल्य प्रकार में पैरामीटर रहित निर्माता नहीं हैं। (कुछ समय प्रतिबिंब के साथ मूल्य प्रकार के कंस्ट्रक्टरों को प्राप्त करें - आपको एक पैरामीटर रहित नहीं मिलेगा।)

क्योंकि यह भाषा संगत रहता है यह, एक निर्माता के रूप में "शून्य के साथ एक मूल्य के प्रारंभ" के इलाज के लिए सी # के लिए समझ में आता है - आप सोच सकते हैं new(...)के रूप में हमेशा एक निर्माता बुला। यह CLI के लिए अलग तरह से सोचने के लिए समझ में आता है, क्योंकि कॉल करने के लिए कोई वास्तविक कोड नहीं है - और निश्चित रूप से कोई टाइप-विशिष्ट कोड नहीं है।

इससे भी फर्क पड़ता है कि आपने इसे इनिशियलाइज़ करने के बाद वैल्यू के साथ क्या करने जा रहे हैं। आईएल के लिए इस्तेमाल किया

Guid localVariable = new Guid(someString);

के लिए इस्तेमाल IL के लिए अलग है:

myInstanceOrStaticVariable = new Guid(someString);

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

using System;

public class Test
{
    static Guid field;

    static void Main() {}
    static void MethodTakingGuid(Guid guid) {}


    static void ParameterisedCtorAssignToField()
    {
        field = new Guid("");
    }

    static void ParameterisedCtorAssignToLocal()
    {
        Guid local = new Guid("");
        // Force the value to be used
        local.ToString();
    }

    static void ParameterisedCtorCallMethod()
    {
        MethodTakingGuid(new Guid(""));
    }

    static void ParameterlessCtorAssignToField()
    {
        field = new Guid();
    }

    static void ParameterlessCtorAssignToLocal()
    {
        Guid local = new Guid();
        // Force the value to be used
        local.ToString();
    }

    static void ParameterlessCtorCallMethod()
    {
        MethodTakingGuid(new Guid());
    }
}

यहाँ वर्ग के लिए IL है, अप्रासंगिक बिट्स (जैसे कि नोड्स) को छोड़कर:

.class public auto ansi beforefieldinit Test extends [mscorlib]System.Object    
{
    // Removed Test's constructor, Main, and MethodTakingGuid.

    .method private hidebysig static void ParameterisedCtorAssignToField() cil managed
    {
        .maxstack 8
        L_0001: ldstr ""
        L_0006: newobj instance void [mscorlib]System.Guid::.ctor(string)
        L_000b: stsfld valuetype [mscorlib]System.Guid Test::field
        L_0010: ret     
    }

    .method private hidebysig static void ParameterisedCtorAssignToLocal() cil managed
    {
        .maxstack 2
        .locals init ([0] valuetype [mscorlib]System.Guid guid)    
        L_0001: ldloca.s guid    
        L_0003: ldstr ""    
        L_0008: call instance void [mscorlib]System.Guid::.ctor(string)    
        // Removed ToString() call
        L_001c: ret
    }

    .method private hidebysig static void ParameterisedCtorCallMethod() cil  managed    
    {   
        .maxstack 8
        L_0001: ldstr ""
        L_0006: newobj instance void [mscorlib]System.Guid::.ctor(string)
        L_000b: call void Test::MethodTakingGuid(valuetype [mscorlib]System.Guid)
        L_0011: ret     
    }

    .method private hidebysig static void ParameterlessCtorAssignToField() cil managed
    {
        .maxstack 8
        L_0001: ldsflda valuetype [mscorlib]System.Guid Test::field
        L_0006: initobj [mscorlib]System.Guid
        L_000c: ret 
    }

    .method private hidebysig static void ParameterlessCtorAssignToLocal() cil managed
    {
        .maxstack 1
        .locals init ([0] valuetype [mscorlib]System.Guid guid)
        L_0001: ldloca.s guid
        L_0003: initobj [mscorlib]System.Guid
        // Removed ToString() call
        L_0017: ret 
    }

    .method private hidebysig static void ParameterlessCtorCallMethod() cil managed
    {
        .maxstack 1
        .locals init ([0] valuetype [mscorlib]System.Guid guid)    
        L_0001: ldloca.s guid
        L_0003: initobj [mscorlib]System.Guid
        L_0009: ldloc.0 
        L_000a: call void Test::MethodTakingGuid(valuetype [mscorlib]System.Guid)
        L_0010: ret 
    }

    .field private static valuetype [mscorlib]System.Guid field
}

जैसा कि आप देख सकते हैं, कंस्ट्रक्टर को कॉल करने के लिए बहुत सारे अलग-अलग निर्देश हैं:

  • newobj: स्टैक पर मूल्य आवंटित करता है, एक पैरामीटर निर्मित निर्माता को कॉल करता है। मध्यवर्ती मूल्यों के लिए उपयोग किया जाता है, उदाहरण के लिए किसी क्षेत्र में असाइनमेंट के लिए या विधि तर्क के रूप में उपयोग करें।
  • call instance: पहले से आवंटित भंडारण स्थान (चाहे स्टैक पर हो या नहीं) का उपयोग करता है। इसका उपयोग स्थानीय चर को निर्दिष्ट करने के लिए ऊपर दिए गए कोड में किया जाता है। यदि एक ही स्थानीय चर को कई newकॉल का उपयोग करके कई बार एक मान दिया जाता है , तो यह पुराने मूल्य के शीर्ष पर डेटा को इनिशियलाइज़ करता है - यह हर बार अधिक स्टैक स्पेस आवंटित नहीं करता है
  • initobj: पहले से आवंटित भंडारण स्थान का उपयोग करता है और डेटा को मिटा देता है। इसका उपयोग हमारे सभी पैरामीटर रहित कॉलर्स के लिए किया जाता है, जिनमें एक स्थानीय वैरिएबल भी शामिल है। विधि कॉल के लिए, एक मध्यवर्ती स्थानीय चर प्रभावी रूप से पेश किया जाता है, और इसके मूल्य को मिटा दिया जाता है initobj

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

void HowManyStackAllocations()
{
    Guid guid = new Guid();
    // [...] Use guid
    guid = new Guid(someBytes);
    // [...] Use guid
    guid = new Guid(someString);
    // [...] Use guid
}

उस "तार्किक रूप से" में 4 स्टैक आवंटन हैं - एक चर के लिए, और प्रत्येक तीन newकॉल के लिए - लेकिन वास्तव में (उस विशिष्ट कोड के लिए) स्टैक केवल एक बार आवंटित किया जाता है, और फिर उसी भंडारण स्थान का पुन: उपयोग किया जाता है।

संपादित करें: बस स्पष्ट होने के लिए, यह केवल कुछ मामलों में सच है ... विशेष रूप से, guidयदि Guidनिर्माता एक अपवाद फेंकता है , तो इसका मूल्य दिखाई नहीं देगा , यही कारण है कि सी # संकलक समान स्टैक स्लॉट का पुन: उपयोग करने में सक्षम है। अधिक विवरण के लिए एरिक लिपर्ट के ब्लॉग पोस्ट को अधिक विवरण और ऐसे मामले में देखें जहां यह लागू नहीं होता है।

मैंने इस उत्तर को लिखने में बहुत कुछ सीखा है - कृपया स्पष्टीकरण के लिए पूछें कि क्या यह स्पष्ट नहीं है!


1
जॉन, HowManyStackAllocations उदाहरण कोड अच्छा है। लेकिन क्या आप इसे गाइड के बजाय स्ट्रक्चर का उपयोग करने के लिए बदल सकते हैं, या एक नया स्ट्रक्चर उदाहरण जोड़ सकते हैं। मुझे लगता है कि तब सीधे केदार के मूल प्रश्न का पता चलेगा।
ऐश

9
मार्गदर्शन पहले से ही एक संरचना है। देखें msdn.microsoft.com/en-us/library/system.guid.aspx मैंने इस प्रश्न के लिए संदर्भ प्रकार नहीं चुना है :)
जॉन स्कीट

1
क्या होता है जब आपके पास होता है List<Guid>और उन 3 को इसमें जोड़ते हैं? वह 3 आवंटन (समान IL) होगा? लेकिन उन्हें कहीं जादुई रखा गया है
Arec Barrwin

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

2
@ वाणी: वास्तव में, एरिक ने अपने पोस्ट के निचले हिस्से के पास यह कहा: "अब, वेस्नर की बात के बारे में क्या? हाँ, वास्तव में अगर यह एक स्टैक-आवंटित स्थानीय चर है (और एक बंद में एक क्षेत्र नहीं) जो घोषित किया गया है। कंस्ट्रक्टर कॉल के रूप में नेस्टिंग के "नेस्ट" के समान स्तर पर तब हम एक नया अस्थायी बनाने, अस्थायी को इनिशियलाइज़ करने, और इसे लोकल पर कॉपी करने की इस कठोरता से नहीं गुजरते हैं। उस विशिष्ट (और आम) मामले में हम ऑप्टिमाइज़ कर सकते हैं। अस्थायी और कॉपी का निर्माण क्योंकि अंतर का निरीक्षण करना C # कार्यक्रम के लिए असंभव है! "
जॉन स्कीट

40

परिस्थितियों के आधार पर किसी संरचना के फ़ील्ड वाली मेमोरी को स्टैक या हीप पर आवंटित किया जा सकता है। यदि संरचना-प्रकार चर एक स्थानीय चर या पैरामीटर है जो कुछ गुमनाम प्रतिनिधि या पुनरावृत्त वर्ग द्वारा कब्जा नहीं किया जाता है, तो इसे स्टैक पर आवंटित किया जाएगा। यदि चर कुछ वर्ग का हिस्सा है, तो यह ढेर पर वर्ग के भीतर आवंटित किया जाएगा।

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

इसी तरह स्टैक पर आवंटित संरचनाओं के लिए, सिवाय इसके कि C # का उपयोग करने से पहले सभी स्थानीय चरों को कुछ मूल्य पर सेट करने की आवश्यकता होती है, इसलिए आपको या तो एक कस्टम कंस्ट्रक्टर या डिफ़ॉल्ट कंस्ट्रक्टर को कॉल करना होगा (एक कंस्ट्रक्टर जो कोई पैरामीटर नहीं लेता है, हमेशा उपलब्ध होता है) structs)।


13

इसे कॉम्पैक्ट रूप से रखने के लिए, नया, स्ट्रक्चर्स के लिए एक मिथ्या नाम है, नए को बस कंस्ट्रक्टर कहते हैं। संरचना के लिए एकमात्र भंडारण स्थान वह स्थान है जिसे परिभाषित किया गया है।

यदि यह एक सदस्य चर है, तो इसे सीधे उसी में संग्रहीत किया जाता है, जिसमें इसे परिभाषित किया गया है, यदि यह एक स्थानीय चर या पैरामीटर है तो इसे स्टैक पर संग्रहीत किया जाता है।

इसे उन वर्गों के विपरीत करें, जहां एक संदर्भ है जहां संरचना अपनी संपूर्णता में संग्रहीत की गई होगी, जबकि संदर्भ ढेर पर कहीं इंगित करता है। (सदस्य, स्थानीय / स्टैक पर पैरामीटर)

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


5

सभी प्रकार के मानों के साथ, संरचनाएं हमेशा वहीं जाती हैं जहां उन्हें घोषित किया गया था

इस प्रश्न को और अधिक विवरण के लिए यहां देखें कि संरचना का उपयोग कब करना है। और यह सवाल यहां कुछ और जानकारी के लिए है।

संपादित करें: मैंने गलत उत्तर दिया था कि वे हमेशा स्टैक में जाते हैं। यह गलत है


"संरचनाएं हमेशा वहां जाती हैं जहां उन्हें घोषित किया गया था", यह थोड़ा भ्रामक है। एक कक्षा में एक संरचनात्मक क्षेत्र को हमेशा "गतिशील मेमोरी में रखा जाता है जब प्रकार का एक उदाहरण निर्मित होता है" - जेफ रिक्टर। यह अप्रत्यक्ष रूप से ढेर पर हो सकता है, लेकिन सामान्य संदर्भ प्रकार के समान नहीं है।
ऐश

नहीं, मुझे लगता है कि यह बिल्कुल सही है - भले ही यह एक संदर्भ प्रकार के समान नहीं है। एक चर का मूल्य रहता है जहां यह घोषित किया जाता है। एक संदर्भ प्रकार चर का मान वास्तविक डेटा के बजाय एक संदर्भ है।
जॉन स्कीट

सारांश में, जब भी आप एक विधि को कहीं भी बनाते हैं (घोषित करते हैं) तो इसे हमेशा स्टैक पर बनाया जाता है।
ऐश

2
जॉन, आपको मेरी बात याद आती है। यह प्रश्न पहली बार पूछे जाने का कारण यह है कि यह कई डेवलपर्स के लिए स्पष्ट नहीं है (मुझे शामिल किया गया जब तक मैं सीएलआर वाया सी # नहीं पढ़ता) जहां एक संरचना आवंटित की जाती है यदि आप इसे बनाने के लिए नए ऑपरेटर का उपयोग करते हैं। यह कहना कि "संरचनाएं हमेशा वहां जाती हैं जहां उन्हें घोषित किया गया था" स्पष्ट उत्तर नहीं है।
ऐश

1
@Ash: यदि मेरे पास समय है, तो मुझे काम मिलने पर उत्तर लिखने का प्रयास करना होगा। यह ट्रेन पर कवर करने की कोशिश करने के लिए बहुत बड़ा विषय है, हालांकि :)
जॉन स्कीट

4

मैं शायद यहां कुछ याद कर रहा हूं लेकिन हम आवंटन के बारे में क्यों परवाह करते हैं?

मूल्य प्रकार मूल्य से पारित हो जाते हैं;) और इस तरह उन्हें एक अलग दायरे में नहीं बदला जा सकता है जहां वे परिभाषित हैं। उस मूल्य को म्यूट करने में सक्षम होने के लिए जिसे आपने [रेफ] कीवर्ड जोड़ा है।

संदर्भ प्रकार संदर्भ द्वारा पारित किए जाते हैं और इन्हें उत्परिवर्तित किया जा सकता है।

निश्चित रूप से अपरिवर्तनीय संदर्भ प्रकार के तार सबसे लोकप्रिय हैं।

सरणी लेआउट / आरंभ: मान प्रकार -> शून्य मेमोरी [नाम, ज़िप] [नाम, ज़िप] संदर्भ प्रकार -> शून्य मेमोरी -> शून्य [रेफ] [रेफ]


3
संदर्भ प्रकार संदर्भ द्वारा पारित नहीं किए जाते हैं - संदर्भ मूल्य द्वारा पारित किए जाते हैं। वह बहुत अलग है।
जॉन स्कीट

2

एक classया structघोषणा एक खाके की तरह है जो रन टाइम पर इंस्टेंस या ऑब्जेक्ट बनाने के लिए उपयोग किया जाता है। यदि आप एक classया structव्यक्ति को परिभाषित करते हैं , तो व्यक्ति प्रकार का नाम है। यदि आप प्रकार के व्यक्ति के एक चर पी को घोषित और आरंभ करते हैं, तो p को व्यक्ति की वस्तु या उदाहरण कहा जाता है। एक ही व्यक्ति प्रकार के कई उदाहरण बनाए जा सकते हैं, और प्रत्येक उदाहरण में propertiesऔर इसके अलग-अलग मूल्य हो सकते हैं fields

A classएक संदर्भ प्रकार है। जब कोई ऑब्जेक्ट classबनाया जाता है, तो जिस चर को ऑब्जेक्ट असाइन किया जाता है, वह उस मेमोरी का केवल एक संदर्भ रखता है। जब ऑब्जेक्ट संदर्भ को एक नया चर सौंपा जाता है, तो नया चर मूल वस्तु को संदर्भित करता है। एक चर के माध्यम से किए गए परिवर्तन दूसरे चर में परिलक्षित होते हैं क्योंकि वे दोनों एक ही डेटा को संदर्भित करते हैं।

A structमान प्रकार है। जब एक structबनाया जाता है, तो जिस चर को structसौंपा जाता है वह संरचना का वास्तविक डेटा रखता है। जब structएक नए चर को सौंपा जाता है, तो इसे कॉपी किया जाता है। नए चर और मूल चर में एक ही डेटा की दो अलग-अलग प्रतियां होती हैं। एक प्रति में किए गए परिवर्तन दूसरी प्रति को प्रभावित नहीं करते हैं।

सामान्य तौर पर, classesअधिक जटिल व्यवहार को मॉडल करने के लिए उपयोग किया जाता है, या डेटा जिसे किसी classऑब्जेक्ट के बनने के बाद संशोधित करने का इरादा है। Structsछोटे डेटा संरचनाओं के लिए सबसे उपयुक्त होते हैं जिनमें मुख्य रूप से डेटा होता है जिसे बनाने के बाद संशोधित करने का इरादा नहीं structहोता है।

अधिक जानकारी के लिए...


1

बहुत अधिक संरचनाएं जिन्हें मूल्य प्रकार माना जाता है, उन्हें स्टैक पर आवंटित किया जाता है, जबकि ऑब्जेक्ट्स को ढेर पर आवंटित किया जाता है, जबकि ऑब्जेक्ट संदर्भ (पॉइंटर) स्टैक पर आवंटित किया जाता है।


1

स्टैक को संरचनाएं आवंटित की जाती हैं। यहाँ एक उपयोगी व्याख्या है:

structs

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


5
यह उस स्थिति को कवर नहीं करता है जब कोई संरचना किसी वर्ग का हिस्सा होती है - जिस बिंदु पर वह शेष वस्तु के डेटा के साथ ढेर पर रहता है।
जॉन स्कीट

1
हां लेकिन यह वास्तव में पूछे जाने वाले सवाल पर केंद्रित है और इसका जवाब देता है। वोट दिया।
ऐश

... जबकि अभी भी गलत और भ्रामक है। क्षमा करें, लेकिन इस सवाल का कोई छोटा जवाब नहीं है - जेफरी का एकमात्र पूर्ण उत्तर है।
मार्क Gravell
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.