जब आप new
ऑपरेटर के साथ एक वर्ग का एक उदाहरण बनाते हैं , तो मेमोरी को ढेर पर आवंटित किया जाता है। जब आप new
ऑपरेटर के साथ एक संरचना का एक उदाहरण बनाते हैं, जहां मेमोरी आवंटित की जाती है, तो ढेर पर या स्टैक पर?
जब आप new
ऑपरेटर के साथ एक वर्ग का एक उदाहरण बनाते हैं , तो मेमोरी को ढेर पर आवंटित किया जाता है। जब आप new
ऑपरेटर के साथ एक संरचना का एक उदाहरण बनाते हैं, जहां मेमोरी आवंटित की जाती है, तो ढेर पर या स्टैक पर?
जवाबों:
ठीक है, चलो देखते हैं कि क्या मैं इसे किसी भी स्पष्ट कर सकता हूं।
सबसे पहले, ऐश सही है: सवाल यह नहीं है कि मूल्य प्रकार चर कहां आवंटित किए जाते हैं। यह एक अलग सवाल है - और एक जिसका जवाब सिर्फ "स्टैक पर" नहीं है। यह उससे अधिक जटिल है (और 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
निर्माता एक अपवाद फेंकता है , तो इसका मूल्य दिखाई नहीं देगा , यही कारण है कि सी # संकलक समान स्टैक स्लॉट का पुन: उपयोग करने में सक्षम है। अधिक विवरण के लिए एरिक लिपर्ट के ब्लॉग पोस्ट को अधिक विवरण और ऐसे मामले में देखें जहां यह लागू नहीं होता है।
मैंने इस उत्तर को लिखने में बहुत कुछ सीखा है - कृपया स्पष्टीकरण के लिए पूछें कि क्या यह स्पष्ट नहीं है!
List<Guid>
और उन 3 को इसमें जोड़ते हैं? वह 3 आवंटन (समान IL) होगा? लेकिन उन्हें कहीं जादुई रखा गया है
guid
केवल आधा-ओवरराइट किया गया है, क्योंकि यह वैसे भी दिखाई नहीं देगा।
परिस्थितियों के आधार पर किसी संरचना के फ़ील्ड वाली मेमोरी को स्टैक या हीप पर आवंटित किया जा सकता है। यदि संरचना-प्रकार चर एक स्थानीय चर या पैरामीटर है जो कुछ गुमनाम प्रतिनिधि या पुनरावृत्त वर्ग द्वारा कब्जा नहीं किया जाता है, तो इसे स्टैक पर आवंटित किया जाएगा। यदि चर कुछ वर्ग का हिस्सा है, तो यह ढेर पर वर्ग के भीतर आवंटित किया जाएगा।
यदि संरचना को ढेर पर आवंटित किया जाता है, तो मेमोरी को आवंटित करने के लिए नए ऑपरेटर को कॉल करना वास्तव में आवश्यक नहीं है। एकमात्र उद्देश्य यह होगा कि निर्माणकर्ता में जो कुछ भी है उसके अनुसार क्षेत्र मूल्यों को निर्धारित किया जाए। यदि कंस्ट्रक्टर को नहीं बुलाया जाता है, तो सभी फ़ील्ड अपने डिफ़ॉल्ट मान (0 या शून्य) प्राप्त करेंगे।
इसी तरह स्टैक पर आवंटित संरचनाओं के लिए, सिवाय इसके कि C # का उपयोग करने से पहले सभी स्थानीय चरों को कुछ मूल्य पर सेट करने की आवश्यकता होती है, इसलिए आपको या तो एक कस्टम कंस्ट्रक्टर या डिफ़ॉल्ट कंस्ट्रक्टर को कॉल करना होगा (एक कंस्ट्रक्टर जो कोई पैरामीटर नहीं लेता है, हमेशा उपलब्ध होता है) structs)।
इसे कॉम्पैक्ट रूप से रखने के लिए, नया, स्ट्रक्चर्स के लिए एक मिथ्या नाम है, नए को बस कंस्ट्रक्टर कहते हैं। संरचना के लिए एकमात्र भंडारण स्थान वह स्थान है जिसे परिभाषित किया गया है।
यदि यह एक सदस्य चर है, तो इसे सीधे उसी में संग्रहीत किया जाता है, जिसमें इसे परिभाषित किया गया है, यदि यह एक स्थानीय चर या पैरामीटर है तो इसे स्टैक पर संग्रहीत किया जाता है।
इसे उन वर्गों के विपरीत करें, जहां एक संदर्भ है जहां संरचना अपनी संपूर्णता में संग्रहीत की गई होगी, जबकि संदर्भ ढेर पर कहीं इंगित करता है। (सदस्य, स्थानीय / स्टैक पर पैरामीटर)
यह सी ++ में थोड़ा देखने में मदद कर सकता है, जहां कक्षा / संरचना के बीच वास्तविक अंतर नहीं है। (भाषा में समान नाम हैं, लेकिन वे केवल चीजों की डिफ़ॉल्ट पहुंच को संदर्भित करते हैं) जब आप नया कॉल करते हैं तो आपको ढेर स्थान पर एक संकेतक मिलता है, जबकि यदि आपके पास एक गैर-सूचक संदर्भ है तो इसे सीधे स्टैक पर संग्रहीत किया जाता है या अन्य ऑब्जेक्ट के भीतर, एलए सी # में संरचना करता है।
सभी प्रकार के मानों के साथ, संरचनाएं हमेशा वहीं जाती हैं जहां उन्हें घोषित किया गया था ।
इस प्रश्न को और अधिक विवरण के लिए यहां देखें कि संरचना का उपयोग कब करना है। और यह सवाल यहां कुछ और जानकारी के लिए है।
संपादित करें: मैंने गलत उत्तर दिया था कि वे हमेशा स्टैक में जाते हैं। यह गलत है ।
मैं शायद यहां कुछ याद कर रहा हूं लेकिन हम आवंटन के बारे में क्यों परवाह करते हैं?
मूल्य प्रकार मूल्य से पारित हो जाते हैं;) और इस तरह उन्हें एक अलग दायरे में नहीं बदला जा सकता है जहां वे परिभाषित हैं। उस मूल्य को म्यूट करने में सक्षम होने के लिए जिसे आपने [रेफ] कीवर्ड जोड़ा है।
संदर्भ प्रकार संदर्भ द्वारा पारित किए जाते हैं और इन्हें उत्परिवर्तित किया जा सकता है।
निश्चित रूप से अपरिवर्तनीय संदर्भ प्रकार के तार सबसे लोकप्रिय हैं।
सरणी लेआउट / आरंभ: मान प्रकार -> शून्य मेमोरी [नाम, ज़िप] [नाम, ज़िप] संदर्भ प्रकार -> शून्य मेमोरी -> शून्य [रेफ] [रेफ]
एक class
या struct
घोषणा एक खाके की तरह है जो रन टाइम पर इंस्टेंस या ऑब्जेक्ट बनाने के लिए उपयोग किया जाता है। यदि आप एक class
या struct
व्यक्ति को परिभाषित करते हैं , तो व्यक्ति प्रकार का नाम है। यदि आप प्रकार के व्यक्ति के एक चर पी को घोषित और आरंभ करते हैं, तो p को व्यक्ति की वस्तु या उदाहरण कहा जाता है। एक ही व्यक्ति प्रकार के कई उदाहरण बनाए जा सकते हैं, और प्रत्येक उदाहरण में properties
और इसके अलग-अलग मूल्य हो सकते हैं fields
।
A class
एक संदर्भ प्रकार है। जब कोई ऑब्जेक्ट class
बनाया जाता है, तो जिस चर को ऑब्जेक्ट असाइन किया जाता है, वह उस मेमोरी का केवल एक संदर्भ रखता है। जब ऑब्जेक्ट संदर्भ को एक नया चर सौंपा जाता है, तो नया चर मूल वस्तु को संदर्भित करता है। एक चर के माध्यम से किए गए परिवर्तन दूसरे चर में परिलक्षित होते हैं क्योंकि वे दोनों एक ही डेटा को संदर्भित करते हैं।
A struct
मान प्रकार है। जब एक struct
बनाया जाता है, तो जिस चर को struct
सौंपा जाता है वह संरचना का वास्तविक डेटा रखता है। जब struct
एक नए चर को सौंपा जाता है, तो इसे कॉपी किया जाता है। नए चर और मूल चर में एक ही डेटा की दो अलग-अलग प्रतियां होती हैं। एक प्रति में किए गए परिवर्तन दूसरी प्रति को प्रभावित नहीं करते हैं।
सामान्य तौर पर, classes
अधिक जटिल व्यवहार को मॉडल करने के लिए उपयोग किया जाता है, या डेटा जिसे किसी class
ऑब्जेक्ट के बनने के बाद संशोधित करने का इरादा है। Structs
छोटे डेटा संरचनाओं के लिए सबसे उपयुक्त होते हैं जिनमें मुख्य रूप से डेटा होता है जिसे बनाने के बाद संशोधित करने का इरादा नहीं struct
होता है।
बहुत अधिक संरचनाएं जिन्हें मूल्य प्रकार माना जाता है, उन्हें स्टैक पर आवंटित किया जाता है, जबकि ऑब्जेक्ट्स को ढेर पर आवंटित किया जाता है, जबकि ऑब्जेक्ट संदर्भ (पॉइंटर) स्टैक पर आवंटित किया जाता है।
स्टैक को संरचनाएं आवंटित की जाती हैं। यहाँ एक उपयोगी व्याख्या है:
इसके अतिरिक्त, .NET में त्वरित रूप से वर्गीकृत की जाने वाली कक्षाएं हीप या .NET के आरक्षित मेमोरी स्पेस पर मेमोरी आवंटित करती हैं। जबकि स्टैक पर आवंटन के कारण त्वरित होने पर संरचना अधिक दक्षता प्राप्त करती है। इसके अलावा, यह ध्यान दिया जाना चाहिए कि संरचनाओं के भीतर गुजरने वाले मापदंडों को मूल्य के आधार पर किया जाता है।