2008-10-15 11 views
254

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

उत्तर

283

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

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

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

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

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


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

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

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

myInstanceOrStaticVariable = new Guid(someString); 

इसके अलावा, मूल्य एक मध्यवर्ती मूल्य के रूप में प्रयोग किया जाता है, उदाहरण के लिए: आईएल

Guid localVariable = 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()); 
    } 
} 

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

.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 कन्स्ट्रक्टर अपवाद फेंकता है, यही कारण है कि सी # कंपाइलर सक्षम है एक ही ढेर स्लॉट का पुन: उपयोग करें। अधिक जानकारी के लिए एरिक लिपर्ट के blog post on value type construction देखें और एक मामला जहां लागू नहीं होता है।

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

+0

अच्छा जवाब जॉन, इस क्षेत्र की जटिलता का स्पष्टीकरण बहुत महत्वपूर्ण है। मुझे अब पता होना चाहिए कि ज्यादातर सॉफ्टवेयर विकास प्रश्नों के लिए बहुत ही कम काला और सफेद जवाब है। – Ash

+1

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

+6

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

4

सभी मूल्य प्रकारों के साथ, structs हमेशा घोषित घोषित किए गए थे।

structs का उपयोग करने के बारे में अधिक जानकारी के लिए इस प्रश्न को here देखें। और इस प्रश्न here structs पर कुछ और जानकारी के लिए।

संपादित करें: मैं था mistankely जवाब है कि वे हमेशा ढेर में जाना। यह incorrect है।

+0

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

+0

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

+0

संक्षेप में, जब भी आप एक विधि में किसी भी प्रकार का मूल्य (घोषणा) बनाते हैं तो यह हमेशा ढेर पर बनाया जाता है। – Ash

0

स्ट्रक्क्स को ढेर में आवंटित किया जाता है। यहाँ एक उपयोगी विवरण है:

Structs

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

+5

यह उस स्थिति को कवर नहीं करता है जब एक वर्ग कक्षा का हिस्सा होता है - जिस बिंदु पर यह ऑब्जेक्ट के बाकी हिस्सों के साथ ढेर पर रहता है। –

+1

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

+0

... जबकि अभी भी गलत और भ्रामक है। क्षमा करें, लेकिन इस सवाल का कोई संक्षिप्त जवाब नहीं है - जेफरी का एकमात्र पूरा जवाब है। –

32

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

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

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

1

बहुत सारे structs जिन्हें मान प्रकार माना जाता है, को ढेर पर आवंटित किया जाता है, जबकि वस्तुओं को ढेर पर आवंटित किया जाता है, जबकि वस्तु संदर्भ (सूचक) ढेर पर आवंटित किया जाता है।

8

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

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

कक्षाओं में इसकी तुलना करें, जहां संदर्भ पूरी तरह से संग्रहीत किया गया हो, जहां संदर्भ ढेर पर कहीं भी संदर्भित होता है। (भीतर सदस्य, स्थानीय/पैरामीटर पर पैरामीटर)

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

3

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

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

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

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

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

+2

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

2

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

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

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

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

for more...

संबंधित मुद्दे