C++ रणनीति लिखने से पहले कुछ बुनियादी ज्ञान जानना आवश्यक है, कम से कम इन नियमों को जानना आवश्यक है। नीचे दिए गए स्रोतों को पुनः प्राप्त करें
यदि कोई व्यक्ति स्वयं को प्रोग्रामर कहता है, लेकिन मेमोरी के बारे में कुछ नहीं जानता है, तो मैं आपको बता सकता हूं कि वह निश्चित रूप से बकवास कर रहा है। C या C ++ प्रोग्राम लिखने के लिए, मेमोरी पर अधिक ध्यान देने की आवश्यकता है, न केवल इसलिए कि मेमोरी का उचित वितरण सीधे कार्यक्रम की दक्षता और प्रदर्शन को प्रभावित करता है, बल्कि इससे भी महत्वपूर्ण बात यह है कि जब हम मेमोरी का उपयोग करते हैं तो समस्याएं होती हैं, और कई बार, ये समस्याएं आसानी से पता नहीं लगती हैं, जैसे कि मेमोरी रिसाव, जैसे कि निलंबित कुंजी। मैं आज यहां इन समस्याओं से बचने के बारे में बात नहीं करना चाहता हूं, बल्कि एक और दृष्टिकोण से C ++ मेमोरी ऑब्जेक्ट को जानना चाहता हूं।
हम जानते हैं कि C++ मेमोरी को तीन तार्किक क्षेत्रों में विभाजित करता हैः स्टैक, हेड और स्टैटिक स्टोरेज। इस प्रकार, मैं उन में स्थित वस्तुओं को स्टैक ऑब्जेक्ट, हेड ऑब्जेक्ट और स्टैटिक ऑब्जेक्ट कहता हूं। तो इन विभिन्न मेमोरी ऑब्जेक्टों में क्या अंतर है? स्टैक ऑब्जेक्ट और हेड ऑब्जेक्ट में क्या अंतर है? स्टैक ऑब्जेक्ट या हेड ऑब्जेक्ट बनाने से कैसे मना किया जाए? ये आज के विषय हैं।
1 मूल अवधारणा
सबसे पहले, हम देखेंगे कि
Type stack_object ;
stack_object एक अलंकार ऑब्जेक्ट है जिसका जीवनकाल परिभाषा बिंदु से शुरू होता है और जब यह फ़ंक्शन वापस आता है तो उसका जीवन समाप्त हो जाता है।
इसके अलावा, लगभग सभी अस्थायी वस्तुएं तालिका वस्तुएं हैं. उदाहरण के लिए, निम्नलिखित फ़ंक्शन परिभाषाएंः
Type fun(Type object);
यह फ़ंक्शन कम से कम दो अस्थायी ऑब्जेक्ट उत्पन्न करता है, सबसे पहले, पैरामीटर को मान के आधार पर पास किया जाता है, इसलिए कॉपी कंस्ट्रक्टर फ़ंक्शन को कॉल करने से एक अस्थायी ऑब्जेक्ट object_copy1 उत्पन्न होता है, जिसका उपयोग फ़ंक्शन के भीतर किया जाता है, object_copy1 के बजाय object_copy1 का उपयोग नहीं किया जाता है, स्वाभाविक रूप से, object_copy1 एक कंक्रीट ऑब्जेक्ट है, जिसे फ़ंक्शन के रिटर्न पर जारी किया जाता है; और यह फ़ंक्शन एक मान वापस करता है, जब फ़ंक्शन वापस आता है, तो एक अस्थायी ऑब्जेक्ट object_copy2 भी उत्पन्न होता है, यदि हम रिटर्न वैल्यू ऑप्टिमाइज़ेशन (NRV) को ध्यान में नहीं रखते हैं, तो यह अस्थायी ऑब्जेक्ट फ़ंक्शन के रिटर्न के कुछ समय बाद जारी किया जाता है। उदाहरण के लिए, किसी फ़ंक्शन में निम्न कोड हैः
Type tt ,result ; //生成两个栈对象
tt = fun(tt); //函数返回时,生成的是一个临时对象object_copy2
उपरोक्त दूसरे कथन का कार्यान्वयन इस प्रकार है, पहले फ़ंक्शन fun लौटने पर एक अस्थायी ऑब्जेक्ट object_copy2 उत्पन्न करता है, और फिर असाइनमेंट ऑपरेटर को निष्पादित करने के लिए कॉल करता है।
tt = object_copy2 ; //调用赋值运算符
क्या आप देख सकते हैं कि कम्पाइलर हमारे लिए इतने सारे अस्थायी ऑब्जेक्ट उत्पन्न करता है, जबकि हम इसके बारे में नहीं जानते हैं, और इन अस्थायी ऑब्जेक्टों को उत्पन्न करने के लिए समय और स्थान की लागत बहुत अधिक हो सकती है, इसलिए, आप शायद समझ सकते हैं कि क्यों कंक्रीट ऑब्जेक्ट्स के लिए यह बेहतर है कि वे मूल्य के आधार पर फ़ंक्शन पैरामीटर को पारित करने के बजाय कॉन्स्ट संदर्भ के साथ पारित करें।
इसके बाद, स्टैक को देखें. स्टैक, जिसे मुक्त भंडारण क्षेत्र भी कहा जाता है, को प्रोग्राम के निष्पादन के दौरान गतिशील रूप से आवंटित किया जाता है, इसलिए इसकी सबसे बड़ी विशेषता गतिशीलता है. सी ++ में, सभी स्टैक ऑब्जेक्ट को बनाने और नष्ट करने के लिए प्रोग्रामर जिम्मेदार हैं, इसलिए, यदि इसे गलत तरीके से संसाधित किया जाता है, तो मेमोरी समस्याएं हो सकती हैं। यदि स्टैक ऑब्जेक्ट आवंटित किया जाता है, लेकिन जारी करना भूल जाता है, तो मेमोरी रिसाव होता है; और यदि ऑब्जेक्ट को मुक्त कर दिया जाता है, लेकिन संबंधित संकेतक को NULL में नहीं रखा जाता है, तो यह संकेतक एक तथाकथित लंबित संकेतक है, जिसे फिर से इस संकेतक का उपयोग करने पर अवैध पहुंच होती है, जिससे गंभीर कार्यक्रम दुर्घटनाग्रस्त हो जाता है।
तो, C++ में स्टैक ऑब्जेक्ट्स को कैसे असाइन किया जाता है? एकमात्र तरीका है new (बेशक, Malloc कमांड का उपयोग करके भी C स्टैक मेमोरी प्राप्त की जा सकती है), जो स्टैक में एक स्टॉक को असाइन करता है और उस ऑब्जेक्ट को इंगित करने वाले पॉइंटर्स को वापस करता है।
फिर से स्थिर भंडारण क्षेत्र को देखें. सभी स्थिर वस्तुओं, वैश्विक वस्तुओं को स्थिर भंडारण क्षेत्र में आवंटित किया जाता है. वैश्विक वस्तुओं के बारे में, यह मुख्य))) फ़ंक्शन के निष्पादन से पहले आवंटित किया जाता है। वास्तव में, मुख्य))) फ़ंक्शन में प्रदर्शित कोड निष्पादित होने से पहले, एक कंपाइलर द्वारा उत्पन्न मुख्य))) फ़ंक्शन को बुलाया जाता है, जबकि मुख्य))) फ़ंक्शन सभी वैश्विक वस्तुओं के निर्माण और आरंभिकरण का काम करता है। और मुख्य))) फ़ंक्शन समाप्त होने से पहले, सभी वैश्विक वस्तुओं को मुक्त करने के लिए कंपाइलर द्वारा उत्पन्न एक्जिट फ़ंक्शन को बुलाया जाता है। उदाहरण के लिए, निम्न कोडः
void main(void)
{
... // 显式代码
}
// 实际上转化为这样:
void main(void)
{
_main(); //隐式代码,由编译器产生,用以构造所有全局对象
... // 显式代码
...
exit() ; // 隐式代码,由编译器产生,用以释放所有全局对象
}
तो, यह जानकर, हम कुछ युक्तियों को निकाल सकते हैं, जैसे कि, मान लीजिए कि हम main() फ़ंक्शन के निष्पादन से पहले कुछ तैयारी कार्य करना चाहते हैं, तो हम इन तैयारी कार्यों को एक कस्टम ग्लोबल ऑब्जेक्ट के कंस्ट्रक्टर में लिख सकते हैं, ताकि, मुख्य() फ़ंक्शन के एक्सप्रेस कोड निष्पादन से पहले, इस ग्लोबल ऑब्जेक्ट के कंस्ट्रक्टर को बुलाया जाए और अपेक्षित क्रियाएं की जाएं, जिससे हमारा उद्देश्य पूरा हो जाए। अभी हमने स्टैटिक स्टोरेज क्षेत्र में ग्लोबल ऑब्जेक्ट के बारे में बात की है, तो, स्थानीय स्थैतिक ऑब्जेक्ट?
एक और स्थिर वस्तु है, और वह है कि यह कक्षा के स्थिर सदस्य के रूप में है। इस स्थिति को देखते हुए, कुछ अधिक जटिल प्रश्न उठते हैं।
पहली समस्या वर्ग के स्थैतिक सदस्य वस्तुओं के जीवनकाल के बारे में है, कक्षा के स्थैतिक सदस्य वस्तुएं पहले वर्ग वस्तु के निर्माण के साथ उत्पन्न होती हैं और पूरे कार्यक्रम के अंत में समाप्त हो जाती हैं। यानी ऐसी स्थिति मौजूद है कि कार्यक्रम में हम एक वर्ग को परिभाषित करते हैं जिसमें एक स्थैतिक वस्तु सदस्य के रूप में होती है, लेकिन कार्यक्रम के निष्पादन के दौरान, यदि हम उस वर्ग वस्तु में से कोई भी नहीं बनाते हैं, तो उस वर्ग में शामिल स्थैतिक वस्तु उत्पन्न नहीं होती है। यदि कई अन्य वर्ग वस्तुएं बनाई जाती हैं, तो ये सभी वस्तुएं एक स्थैतिक वस्तु सदस्य साझा करती हैं।
दूसरी समस्या यह है कि जब निम्न स्थितियां होती हैंः
class Base
{
public:
static Type s_object ;
}
class Derived1 : public Base / / 公共继承
{
... // other data
}
class Derived2 : public Base / / 公共继承
{
... // other data
}
Base example ;
Derivde1 example1 ;
Derivde2 example2 ;
example.s_object = …… ;
example1.s_object = …… ;
example2.s_object = …… ;
कृपया ध्यान दें कि ऊपर दिए गए तीन blackbody statements के साथ, क्या s_object एक ही ऑब्जेक्ट है जिसे वे एक्सेस कर रहे हैं? जवाब है हां, वे वास्तव में एक ही ऑब्जेक्ट की ओर इशारा करते हैं, जो कि सच नहीं लगता है, है ना? लेकिन यह सच है, आप अपने आप को एक सरल कोड लिख सकते हैं और इसे सत्यापित कर सकते हैं. मैं यह समझाने जा रहा हूं कि ऐसा क्यों होता है? हम जानते हैं कि जब एक प्रकार जैसे कि Derived1, दूसरे प्रकार जैसे Base से विरासत में लेता है, तो इसे एक Derived1 ऑब्जेक्ट के रूप में देखा जा सकता है जिसमें एक Base प्रकार का ऑब्जेक्ट होता है, जो कि एक subobject है। एक Derived1 ऑब्जेक्ट के लिए एक मेमोरी लेआउट के रूप मेंः
अब हम सोचें कि जब हम किसी Derived1 प्रकार के ऑब्जेक्ट को किसी ऐसे फ़ंक्शन को पास करते हैं जो बेसिक प्रकार के पैराग्राफ का संदर्भ नहीं लेता है, तो क्या कटौती होती है? अब आप जानते हैं कि यह केवल Derived1 प्रकार के ऑब्जेक्ट में से एक subobject को बाहर निकालने के बारे में है, जो कि सभी अन्य Derived1 कस्टम डेटा सदस्यों को अनदेखा करता है, और फिर इस subobject को फ़ंक्शन को पास करता है ((वास्तव में, फ़ंक्शन इस subobject की एक प्रति का उपयोग करता है)) ।
सभी वस्तुओं के लिए एक उप-वर्ग है, जो एक उप-वर्ग है, जो एक उप-वर्ग है, जो एक उप-वर्ग है, जो एक उप-वर्ग है, जो एक उप-वर्ग है। सभी वस्तुओं के लिए एक उप-वर्ग है, जो एक उप-वर्ग है, जो एक उप-वर्ग है।
2 तीन प्रकार के मेमोरी ऑब्जेक्ट की तुलना
हेड ऑब्जेक्ट्स का फायदा यह है कि वे उचित समय पर स्वचालित रूप से उत्पन्न होते हैं और उचित समय पर स्वचालित रूप से नष्ट हो जाते हैं, जिससे प्रोग्रामर को चिंता करने की आवश्यकता नहीं होती है; और हेड ऑब्जेक्ट्स का निर्माण आमतौर पर स्टैक ऑब्जेक्ट्स की तुलना में तेज़ होता है, क्योंकि स्टैक ऑब्जेक्ट्स को आवंटित करते समय, ऑपरेटर न्यू ऑपरेशन को बुलाया जाता है, ऑपरेटर न्यू किसी प्रकार के मेमोरी स्पेस सर्च एल्गोरिथ्म का उपयोग करता है, जबकि यह सर्च प्रक्रिया बहुत समय लेने वाली हो सकती है, और हेड ऑब्जेक्ट्स का उत्पादन इतनी परेशानी से कम नहीं होता है, इसके लिए केवल हेड ऑब्जेक्ट्स को स्थानांतरित करने की आवश्यकता होती है। लेकिन ध्यान दें कि आमतौर पर हेड स्पेस की क्षमता अपेक्षाकृत छोटी होती है, आमतौर पर 1MB हेड 2MB, इसलिए बड़े आकार के ऑब्जेक्ट हेड आवंटित करने के लिए उपयुक्त नहीं होते हैं। विशेष रूप से रिटर्न फ़ंक्शन में हेड ऑब्जेक्ट्स का उपयोग करना सबसे अच्छा होता है, क्योंकि रिटर्न कॉल की गहराई के साथ, आवश्यक
स्टैक ऑब्जेक्ट, जिसका निर्माण और विनाश समय प्रोग्रामर द्वारा परिभाषित है, यानी प्रोग्रामर के पास स्टैक ऑब्जेक्ट के जीवन पर पूर्ण नियंत्रण है। हमें अक्सर इस तरह के ऑब्जेक्ट की आवश्यकता होती है, उदाहरण के लिए, हमें एक ऑब्जेक्ट बनाने की आवश्यकता होती है जिसे कई फ़ंक्शंस द्वारा एक्सेस किया जा सकता है, लेकिन हम इसे सार्वभौमिक नहीं बनाना चाहते हैं, तो इस समय एक स्टैक ऑब्जेक्ट बनाना निश्चित रूप से एक अच्छा विकल्प है, और फिर विभिन्न कार्यों के बीच इस स्टैक ऑब्जेक्ट के संकेतक को पारित करना, ताकि इस ऑब्जेक्ट को साझा किया जा सके। इसके अलावा, स्टैक की क्षमता बहुत बड़ी है, जो कि कंक्रीट स्पेस की तुलना में है। वास्तव में, जब भौतिक मेमोरी पर्याप्त नहीं है, तो यदि इस समय एक नया स्टैक ऑब्जेक्ट उत्पन्न करने की आवश्यकता है, तो आमतौर पर कोई त्रुटि नहीं होती है, लेकिन सिस्टम वास्तविक भौतिक मेमोरी का विस्तार करने के लिए आभासी मेमोरी का उपयोग करता है।
अब हम स्टैटिक ऑब्जेक्ट्स को देखते हैं।
सबसे पहले, ग्लोबल ऑब्जेक्ट्स; ग्लोबल ऑब्जेक्ट्स इंटर-क्लास और इंटर-फंक्शनल संचार के लिए सबसे सरल तरीका प्रदान करते हैं, हालांकि यह तरीका सुरुचिपूर्ण नहीं है; सामान्य तौर पर, पूरी तरह से ऑब्जेक्ट-उन्मुख भाषाओं में, ग्लोबल ऑब्जेक्ट्स मौजूद नहीं हैं, जैसे कि सी #, क्योंकि ग्लोबल ऑब्जेक्ट्स का मतलब असुरक्षित और उच्च-एडेप्टिविटी है, और कार्यक्रम में ग्लोबल ऑब्जेक्ट्स का बहुत अधिक उपयोग करने से कार्यक्रम की मजबूती, स्थिरता, रखरखाव और दोहराव में भारी कमी आएगी; सी ++ भी ग्लोबल ऑब्जेक्ट्स को पूरी तरह से खत्म कर सकता है, लेकिन अंततः नहीं, मुझे लगता है कि एक कारण सी संगतता है।
इसके बाद वर्ग के स्थिर सदस्य होते हैं, जैसा कि ऊपर उल्लेख किया गया है, मूल वर्ग और उसके सभी व्युत्पन्न वर्ग इस स्थिर सदस्य वस्तु को साझा करते हैं, इसलिए जब इन वर्गों के बीच या इन वर्ग वस्तुओं के बीच डेटा साझा करने या संचार करने की आवश्यकता होती है, तो इस तरह के स्थिर सदस्य निश्चित रूप से एक अच्छा विकल्प हैं।
इसके बाद स्थिर स्थानीय वस्तुएं होती हैं, जो मुख्य रूप से उस समय के मध्यस्थ अवस्था को रखने के लिए उपयोग की जाती हैं जब उस वस्तु के स्थित फ़ंक्शन को आवर्ती रूप से बुलाया जाता है, जिनमें से सबसे प्रमुख उदाहरण एक पुनरावर्ती फ़ंक्शन है, हम सभी जानते हैं कि पुनरावर्ती फ़ंक्शन अपने स्वयं के फ़ंक्शन को बुलाता है, यदि एक गैर-स्थिर स्थानीय वस्तु को पुनरावर्ती फ़ंक्शन में परिभाषित किया जाता है, तो जब पुनरावर्तन की संख्या काफी बड़ी होती है, तो इसके परिणामस्वरूप भारी खर्च भी होता है। यह इसलिए है क्योंकि गैर-स्थिर स्थानीय वस्तुएं घन ऑब्जेक्ट हैं, प्रत्येक पुनरावर्ती कॉल के साथ, एक ऐसा ऑब्जेक्ट उत्पन्न होता है, प्रत्येक वापसी के साथ, इस ऑब्जेक्ट को जारी किया जाता है, और, इस तरह के ऑब्जेक्ट केवल वर्तमान कॉल परत तक ही सीमित होते हैं, जो अधिक गहरी नेस्ट और अधिक उथली परतों के लिए अदृश्य होते हैं। प्रत्येक स्तर में अपने स्वयं के स्थानीय वस्तु और पैरामीटर होते हैं।
एक पुनरावर्ती फ़ंक्शन डिजाइन में, एक स्थैतिक वस्तु को एक गैर-स्थैतिक स्थानीय वस्तु (यानी, घन वस्तु) के स्थान पर उपयोग किया जा सकता है, जो न केवल प्रत्येक पुनरावर्ती कॉल और वापसी पर एक गैर-स्थैतिक वस्तु के उत्पादन और रिलीज के खर्च को कम करता है, बल्कि एक स्थैतिक वस्तु को पुनरावर्ती कॉल के मध्यवर्ती राज्य को भी संरक्षित कर सकता है और प्रत्येक कॉल परत के लिए सुलभ है।
3 अवांछित कटाई
जैसा कि पहले बताया गया है, एप्पल ऑब्जेक्ट्स को उचित समय पर बनाया जाता है और फिर उचित समय पर स्वचालित रूप से जारी किया जाता है, यानी एप्पल ऑब्जेक्ट्स में ऑटो-मैनेजमेंट फ़ंक्शन होता है। तो एप्पल ऑब्जेक्ट्स को स्वचालित रूप से किस समय जारी किया जाता है? पहला, अपने जीवनकाल के अंत में; दूसरा, जब इसके फ़ंक्शन में असामान्यताएं होती हैं। आप कह सकते हैं, यह सब सामान्य है, कुछ भी बड़ा नहीं है। हाँ, कुछ भी बड़ा नहीं है। लेकिन अगर हम थोड़ा और गहराई से जाएं, तो शायद कुछ अप्रत्याशित फसल है।
यदि हम संसाधनों को स्टैक में लपेटते हैं, और स्टैक में संसाधनों को मुक्त करने की क्रिया करते हैं, तो संसाधनों की रिसाव की संभावना बहुत कम हो जाती है, क्योंकि स्टैक स्वचालित रूप से संसाधनों को मुक्त कर सकता है, भले ही उनके कार्यों में असामान्यताएं हों। वास्तविक प्रक्रिया इस प्रकार हैः जब फ़ंक्शन असामान्यताओं को छोड़ देता है, तो तथाकथित stack_unwinding होता है, यानी स्टैक में खोला जाता है, क्योंकि यह स्टैक है, जो स्वाभाविक रूप से स्टैक में मौजूद है, इसलिए स्टैक में वापस जाने के दौरान, स्टैक के घटक को निष्पादित किया जाता है, जिससे सीमित संसाधनों को मुक्त किया जाता है। जब तक कि हम अपने स्वयं के संसाधनों को बनाने के लिए उपयोग नहीं करते हैं, तब तक हम अपने स्वयं के संसाधनों को बनाने की आवश्यकता नहीं रखते हैं।
4 स्टैक ऑब्जेक्ट बनाने पर रोक
जैसा कि ऊपर उल्लेख किया गया है, यदि आप किसी प्रकार के स्टैक ऑब्जेक्ट के निर्माण को प्रतिबंधित करने का निर्णय लेते हैं, तो आप स्वयं एक संसाधन पैकेजिंग वर्ग बना सकते हैं, जो केवल कंक्रीट में उत्पन्न हो सकता है, ताकि असामान्य परिस्थितियों में पैकेजिंग संसाधनों को स्वचालित रूप से जारी किया जा सके।
तो फिर स्टैक ऑब्जेक्ट बनाने से कैसे रोकें? हम पहले से ही जानते हैं कि स्टैक ऑब्जेक्ट बनाने का एकमात्र तरीका new ऑपरेशन का उपयोग करना है, यदि हम new का उपयोग करने से रोकते हैं तो क्या यह काम नहीं करता है. आगे, new ऑपरेशन ऑपरेटर new को कॉल करता है, जबकि ऑपरेटर new को पुनः लोड किया जा सकता है. एक तरीका है कि new ऑपरेटर को निजी बनाना है, समरूपता के लिए, यह बेहतर है कि ऑपरेटर को भी निजी में पुनः लोड किया जाए. अब, आप शायद फिर से सवाल पूछ रहे हैं, क्या हेयर ऑब्जेक्ट बनाने के लिए new को कॉल करने की आवश्यकता नहीं है? हाँ, यह आवश्यक नहीं है, क्योंकि हेयर ऑब्जेक्ट बनाने के लिए मेमोरी में खोज करने की आवश्यकता नहीं है, बल्कि सीधे स्टैक कुंजी को समायोजित करना है, जो स्टैक ऑब्जेक्ट के लिए उपयुक्त स्टैक मेमोरी को आवंटित करने के लिए कुंजी को दबाएगा, और सर्च ऑपरेटर new का मुख्य कार्य है।
#include <stdlib.h> //需要用到C式内存分配函数
class Resource ; //代表需要被封装的资源类
class NoHashObject
{
private:
Resource* ptr ;//指向被封装的资源
... ... //其它数据成员
void* operator new(size_t size) //非严格实现,仅作示意之用
{
return malloc(size) ;
}
void operator delete(void* pp) //非严格实现,仅作示意之用
{
free(pp) ;
}
public:
NoHashObject()
{
//此处可以获得需要封装的资源,并让ptr指针指向该资源
ptr = new Resource() ;
}
~NoHashObject()
{
delete ptr ; //释放封装的资源
}
};
NoHashObject अब एक वर्ग है जो स्टैक ऑब्जेक्ट्स को प्रतिबंधित करता है यदि आप निम्न कोड लिखते हैंः
NoHashObject* fp = new NoHashObject (()) ; // संकलन अवधि में त्रुटि!
fp मिटाना;
उपरोक्त कोड संकलित अवधि में त्रुटियों का उत्पादन करता है. ठीक है, अब जब आप जानते हैं कि एक वर्ग को कैसे डिज़ाइन किया जाए जो स्टैक ऑब्जेक्ट को प्रतिबंधित करता है, तो आप शायद मेरे जैसे सवाल हैं, क्या इस प्रकार के स्टैक ऑब्जेक्ट को उत्पन्न नहीं किया जा सकता है, जब NoHashObject की परिभाषा नहीं बदल सकती है? नहीं, क्या कोई तरीका है, जिसे मैं क्रीम हिंसक रूप से तोड़ने के लिए कहता हूं। C++ इतना शक्तिशाली है कि आप इसके साथ जो कुछ भी करना चाहते हैं उसे कर सकते हैं। यहाँ मुख्य रूप से उपयोग की जाने वाली तकनीक पॉइंटर प्रकार के लिए मजबूर रूपांतरण है।
void main(void)
{
char* temp = new char[sizeof(NoHashObject)] ;
//强制类型转换,现在ptr是一个指向NoHashObject对象的指针
NoHashObject* obj_ptr = (NoHashObject*)temp ;
temp = NULL ; //防止通过temp指针修改NoHashObject对象
//再一次强制类型转换,让rp指针指向堆中NoHashObject对象的ptr成员
Resource* rp = (Resource*)obj_ptr ;
//初始化obj_ptr指向的NoHashObject对象的ptr成员
rp = new Resource() ;
//现在可以通过使用obj_ptr指针使用堆中的NoHashObject对象成员了
... ...
delete rp ;//释放资源
temp = (char*)obj_ptr ;
obj_ptr = NULL ;//防止悬挂指针产生
delete [] temp ;//释放NoHashObject对象所占的堆空间。
}
उपरोक्त कार्यान्वयन परेशानी भरा है, और इस तरह के कार्यान्वयन का उपयोग व्यावहारिक रूप से बहुत कम किया जाता है, लेकिन मैं अभी भी एक रास्ता लिखता हूं क्योंकि इसे समझने से हमें सी ++ मेमोरी ऑब्जेक्ट को समझने में मदद मिलती है। उपरोक्त कई मजबूर प्रकार रूपांतरणों के लिए सबसे बुनियादी क्या है? हम इसे इस तरह समझ सकते हैंः
एक मेमोरी में डेटा स्थिर है, और प्रकार हमारे द्वारा पहने गए चश्मे हैं, और जब हम एक चश्मा पहनते हैं, तो हम मेमोरी में डेटा की व्याख्या करने के लिए एक संबंधित प्रकार का उपयोग करते हैं, ताकि विभिन्न व्याख्याओं से अलग-अलग जानकारी प्राप्त हो सके।
जबरदस्ती प्रकार रूपांतरण का अर्थ है कि आप एक और चश्मा बदलते हैं और फिर उसी मेमोरी डेटा को देखते हैं।
यह भी ध्यान दिया जाना चाहिए कि विभिन्न कंपाइलरों द्वारा ऑब्जेक्ट के सदस्य डेटा के लिए लेआउट अलग-अलग हो सकते हैं, उदाहरण के लिए, अधिकांश कंपाइलरों ने NoHashObject के ptr पॉइंटर सदस्य को ऑब्जेक्ट स्पेस के पहले 4 बाइट्स में व्यवस्थित किया है, ताकि यह सुनिश्चित हो सके कि निम्नलिखित कथन का रूपांतरण कार्य जैसा कि हम उम्मीद करते हैं वैसा ही होः
संसाधन* rp = (संसाधन*)obj_ptr;
हालाँकि, यह सभी कंपाइलरों के लिए आवश्यक नहीं है।
चूंकि हम किसी प्रकार के स्टैक ऑब्जेक्ट के उत्पादन पर प्रतिबंध लगा सकते हैं, तो क्या हम एक वर्ग को डिज़ाइन कर सकते हैं ताकि यह हेयर ऑब्जेक्ट का उत्पादन न कर सके?
5 एल्यूमीनियम वस्तुओं के उत्पादन पर प्रतिबंध
जैसा कि पहले उल्लेख किया गया है, एक घन ऑब्जेक्ट बनाने के लिए घन को सही आकार की जगह से हटाने के लिए घन पॉइंटर को स्थानांतरित किया जाता है, और फिर इस स्थान पर सीधे एक घन ऑब्जेक्ट बनाने के लिए एक संबंधित निर्माण फ़ंक्शन को बुलाया जाता है, और जब फ़ंक्शन वापस आता है, तो यह अपने विघटन निर्माण फ़ंक्शन को कॉल करता है और फिर घन पॉइंटर को उस घन की मेमोरी में वापस ले जाता है। इस प्रक्रिया में ऑपरेटर new/delete ऑपरेशन की आवश्यकता नहीं होती है, इसलिए ऑपरेटर new/delete को निजी सेट करना उद्देश्य को पूरा नहीं कर सकता है। बेशक ऊपर की व्याख्या से आप सोच सकते हैंः विघटन निर्माण फ़ंक्शन या निर्माण फ़ंक्शन को निजी सेट करता है, इसलिए सिस्टम निर्माण / विघटन निर्माण फ़ंक्शन का उपयोग नहीं कर सकता है, और निश्चित रूप से घन उत्पन्न करने में ऑब्जेक्ट नहीं हो सकता है।
यह संभव है, और मैं भी ऐसा करने का इरादा रखता हूँ. लेकिन इससे पहले, एक बात स्पष्ट रूप से विचार करने की आवश्यकता है, यह है कि यदि हम निर्माण कार्य को निजी के रूप में सेट करते हैं, तो हम सीधे new के साथ स्टैक ऑब्जेक्ट का उत्पादन नहीं कर सकते हैं, क्योंकि new ऑब्जेक्ट के लिए स्थान आवंटित करने के बाद इसके निर्माण कार्य को भी कॉल करता है. इसलिए, मैं केवल विश्लेषणात्मक कार्य को निजी के रूप में सेट करने का इरादा रखता हूं. आगे, क्या विश्लेषणात्मक कार्य को निजी के रूप में सेट करने के अलावा अन्य प्रभाव भी हैं? इसके अलावा, यह विरासत को भी सीमित करता है।
यदि किसी वर्ग को आधार वर्ग के रूप में उपयोग करने का इरादा नहीं है, तो आमतौर पर इसका विश्लेषणात्मक फ़ंक्शन निजी के रूप में घोषित किया जाता है।
यदि हम एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक ही बार में एक बार में एक बार
class NoStackObject
{
protected:
~NoStackObject() { }
public:
void destroy()
{
delete this ;//调用保护析构函数
}
};
इसके बाद, NoStackObject वर्ग का उपयोग इस तरह से किया जा सकता हैः
NoStackObject* hash_ptr = नया NoStackObject() ;
...... // hash_ptr के लिए निर्देशित ऑब्जेक्ट पर कार्रवाई करें
hash_ptr->destroy (()); .. ओह, क्या यह थोड़ा अजीब लगता है कि हम एक ऑब्जेक्ट को नए के साथ बनाते हैं, लेकिन इसे हटाने के लिए इसे हटाने के बजाय नष्ट करने के लिए। जाहिर है, उपयोगकर्ता इस तरह के एक अजीब तरीके से उपयोग करने के लिए उपयोग नहीं कर रहे हैं। इसलिए, मैंने निर्माण फ़ंक्शन को निजी या संरक्षित करने का फैसला किया। यह ऊपर से बचने की कोशिश की गई समस्या पर वापस आता है, यानी नए के बिना एक ऑब्जेक्ट बनाने का तरीका क्या है? हम इसे एक अप्रत्यक्ष तरीके से कर सकते हैं, यानी यह एक स्थिर सदस्य प्रकार प्रदान करता है जो विशेष रूप से इस प्रकार के ढेर ऑब्जेक्ट बनाने के लिए उपयोग किया जाता है।
class NoStackObject
{
protected:
NoStackObject() { }
~NoStackObject() { }
public:
static NoStackObject* creatInstance()
{
return new NoStackObject() ;//调用保护的构造函数
}
void destroy()
{
delete this ;//调用保护的析构函数
}
};
अब आप NoStackObject वर्ग का उपयोग इस तरह कर सकते हैंः
NoStackObject* hash_ptr = NoStackObject::creatInstance() ;
...... // hash_ptr के लिए निर्देशित ऑब्जेक्ट पर कार्रवाई करें
hash_ptr->destroy() ;
hash_ptr = NULL ; // लटकती प्वाइंटर्स का उपयोग करने से रोकें
अब यह बेहतर लगता है, वस्तुओं को उत्पन्न करने और वस्तुओं को जारी करने का संचालन एक साथ है।
बहुत से सी या सी++ प्रोग्रामर कचरा रीसाइक्लिंग को नापसंद करते हैं, यह मानते हुए कि कचरा रीसाइक्लिंग निश्चित रूप से गतिशील मेमोरी को प्रबंधित करने के लिए अपने आप से कम कुशल है, और यह निश्चित रूप से प्रक्रिया को रोक देगा, जबकि यदि आप मेमोरी प्रबंधन को नियंत्रित करते हैं, तो आवंटन और रिहाई का समय स्थिर है, जिससे कार्यक्रम बंद नहीं होता है। अंत में, कई सी/सी++ प्रोग्रामर दृढ़ता से मानते हैं कि सी/सी++ में कचरा रीसाइक्लिंग तंत्र को लागू नहीं किया जा सकता है। ये गलत धारणाएं कचरा रीसाइक्लिंग एल्गोरिथ्म को समझने की कमी के कारण अनुमानित हैं।
दरअसल, कचरा रीसाइक्लिंग तंत्र धीमा नहीं है और गतिशील मेमोरी के वितरण की तुलना में अधिक कुशल भी है। चूंकि हम केवल वितरण नहीं कर सकते हैं, तो वितरण मेमोरी के समय केवल स्टैक से लगातार नई मेमोरी प्राप्त करने की आवश्यकता होती है, चलती स्टैक के संकेतक पर्याप्त होते हैं; और रिलीज की प्रक्रिया को छोड़ दिया जाता है, जो स्वाभाविक रूप से तेज हो जाती है। आधुनिक कचरा रीसाइक्लिंग एल्गोरिदम बहुत विकसित हो गए हैं, और वृद्धिशील संग्रह एल्गोरिदम पहले से ही कचरा रीसाइक्लिंग प्रक्रिया को चरणबद्ध करने की अनुमति देते हैं, जिससे प्रक्रिया को बाधित करने से बचा जाता है। जबकि पारंपरिक गतिशील मेमोरी प्रबंधन एल्गोरिदम में भी उचित समय पर मेमोरी के टुकड़ों को इकट्ठा करने का काम होता है, जो कचरा रीसाइक्लिंग की तुलना में अधिक फायदेमंद नहीं है।
जबकि कचरा रीसाइक्लिंग के लिए एल्गोरिदम का आधार आमतौर पर स्कैनिंग पर आधारित होता है और वर्तमान में उपयोग किए जाने वाले सभी संभावित मेमोरी ब्लॉक को चिह्नित करता है, जो पहले से ही आवंटित किए गए सभी मेमोरी से अचिह्नित मेमोरी को पुनर्प्राप्त करता है। C/C++ में, कचरा रीसाइक्लिंग की असमर्थता का आधार आमतौर पर उन सभी संभावित मेमोरी ब्लॉकों को सही ढंग से स्कैन करने में असमर्थता पर आधारित होता है जो अभी भी उपयोग किए जाने की संभावना है, हालांकि, जो कुछ भी असंभव लगता है वह वास्तव में संभव नहीं है। सबसे पहले, मेमोरी को स्कैन करके, स्टैक पर गतिशील रूप से आवंटित किए गए मेमोरी को इंगित करने वाले प्वाइंट को आसानी से पहचाना जा सकता है, और यदि कोई पहचान त्रुटि है, तो केवल कुछ गैर-प्वाइंट डेटा को प्वाइंट के रूप में और गैर-प्वाइंट डेटा को प्वाइंट के रूप में नहीं ले जा सकता है। इस प्रकार, कचरा रीसाइक्लिंग की प्रक्रिया केवल रिकवरी को छोड़ देती है और गलतियों को हटा देती है। यदि इसके
कचरा निकालने के समय, बस बीएसएस खंड, डेटा खंड और वर्तमान में उपयोग की जा रही कंक्रीट स्थान को स्कैन करके, संभावित गतिशील मेमोरी संकेतक की मात्रा का पता लगाएं, और संदर्भित मेमोरी को पुनरावर्ती स्कैन करके वर्तमान में उपयोग में सभी गतिशील मेमोरी प्राप्त करें।
यदि आप अपने प्रोजेक्ट के लिए एक अच्छा कचरा रिसाइकिलर लागू करना चाहते हैं, तो मेमोरी प्रबंधन की गति में सुधार करना और यहां तक कि कुल मेमोरी खपत को कम करना संभव है। यदि आप रुचि रखते हैं, तो आप ऑनलाइन कचरा रिसाइकिल के बारे में पहले से मौजूद लेखों और लागू किए गए पुस्तकालयों को खोज सकते हैं, जो एक प्रोग्रामर के लिए विशेष रूप से महत्वपूर्ण है।
अनुवादित सेएचके झांग
#include<stdio.h>
int*fun(){
int k = 12;
return &k;
}
int main(){
int *p = fun();
printf("%d\n", *p);
getchar();
return 0;
}
यह न केवल एक्सेस किया जा सकता है, लेकिन यह भी संशोधित किया जा सकता है, लेकिन यह एक्सेस अनिश्चित है। स्थानीय चर के पते प्रोग्राम के अपने स्टैक में होते हैं, और प्राधिकरण चर के समाप्त होने के बाद, जब तक स्थानीय चर के मेमोरी पते को किसी अन्य चर को नहीं दिया जाता है, तब तक इसका मूल्य मौजूद रहता है। लेकिन यदि इसे संशोधित किया जाता है, तो यह अपेक्षाकृत खतरनाक होता है, क्योंकि यह मेमोरी पता प्रोग्राम के अन्य चर को दिया जा सकता है, जो पॉइंटर द्वारा मजबूर संशोधन के कारण प्रोग्राम को दुर्घटनाग्रस्त कर सकता है।