क्या किसी स्थानीय चर की मेमोरी को इसके दायरे से बाहर पहुँचा जा सकता है?


1028

मेरे पास निम्न कोड है।

#include <iostream>

int * foo()
{
    int a = 5;
    return &a;
}

int main()
{
    int* p = foo();
    std::cout << *p;
    *p = 8;
    std::cout << *p;
}

और कोड बस बिना किसी रन अपवाद के साथ चल रहा है!

आउटपुट था 58

यह कैसे हो सकता है? एक स्थानीय चर की स्मृति अपने समारोह के बाहर दुर्गम नहीं है?


14
यह भी संकलन नहीं होगा; यदि आप गैर-व्यापार को ठीक करते हैं, तो जीसीसी अभी भी चेतावनी देगा address of local variable ‘a’ returned; Invalid write of size 4 [...] Address 0xbefd7114 is just below the stack ptr
वैलेग्रिंड से

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

23
जबरदस्त हंसी। मुझे सवाल पढ़ने और कुछ जवाब देने की ज़रूरत थी, इससे पहले कि मैंने यह भी समझा कि समस्या कहाँ है। क्या यह वास्तव में वेरिएबल के एक्सेस स्कोप के बारे में एक प्रश्न है? आप अपने फ़ंक्शन के बाहर 'a' का उपयोग भी नहीं करते हैं। और यही सब कुछ है। कुछ मेमोरी रेफरेंस के आसपास फेंकना वैरिएबल स्कोप से बिल्कुल अलग विषय है।
एरिकबवर्क

10
द्वैत उत्तर का मतलब है कि डूप प्रश्न नहीं है। बहुत से द्वैध प्रश्न जो यहां प्रस्तावित लोगों ने किए हैं, वे पूरी तरह से अलग-अलग प्रश्न हैं जो एक ही अंतर्निहित लक्षण को संदर्भित करते हैं ... लेकिन प्रश्नकर्ता को यह जानने का तरीका है कि उन्हें खुला रहना चाहिए। मैंने एक पुराने डूप को बंद कर दिया और इसे इस प्रश्न में मिला दिया जो खुला रहना चाहिए क्योंकि इसका बहुत अच्छा उत्तर है।
योएल Spolsky

16
@ जॉयल: यदि यहां उत्तर अच्छा है, तो इसे पुराने प्रश्नों में मिला दिया जाना चाहिए , जिनमें से यह एक दुपट्टा है, अन्य तरीके से नहीं। और यह सवाल वास्तव में यहां प्रस्तावित अन्य सवालों का एक धोखा है और फिर कुछ (भले ही प्रस्तावित में से कुछ दूसरों की तुलना में बेहतर हैं)। ध्यान दें कि मुझे लगता है कि एरिक का जवाब अच्छा है। (वास्तव में, मैंने पुराने प्रश्नों को निस्तारण करने के लिए पुराने प्रश्नों में से किसी एक में उत्तर को मर्ज करने के लिए इस प्रश्न को चिह्नित किया।)
sbi

जवाबों:


4798

यह कैसे हो सकता है? एक स्थानीय चर की स्मृति अपने समारोह के बाहर दुर्गम नहीं है?

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

एक हफ्ते बाद, आप होटल में लौटते हैं, चेक इन नहीं करते हैं, अपने पुराने कमरे में अपनी चोरी की हुई चाबी, और दराज में देखते हैं। आपकी किताब अभी बाकी है। आश्चर्यजनक!

ऐसे कैसे हो सकता है? यदि आपने कमरा किराए पर नहीं लिया है तो होटल के कमरे की दराज की सामग्री दुर्गम नहीं है?

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

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

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

आप नहीं जानते कि क्या होने जा रहा है; जब आपने होटल से बाहर की जाँच की और बाद में अवैध रूप से उपयोग करने के लिए एक कुंजी चुरा ली, तो आपने एक पूर्वानुमान योग्य, सुरक्षित दुनिया में रहने का अधिकार छोड़ दिया क्योंकि आपने सिस्टम के नियमों को तोड़ने का विकल्प चुना था।

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

अपडेट करें

पवित्र भलाई, इस जवाब पर बहुत ध्यान दिया जा रहा है। (मुझे यकीन नहीं है कि क्यों - मैंने इसे केवल एक "मज़ेदार" थोड़ा सादृश्य माना, लेकिन जो भी हो)।

मैंने सोचा कि यह कुछ और तकनीकी विचारों के साथ इसे अपडेट करने के लिए जर्मन हो सकता है।

कंपाइलर कोड जनरेट करने के व्यवसाय में हैं, जो उस प्रोग्राम द्वारा हेरफेर किए गए डेटा के भंडारण का प्रबंधन करता है। मेमोरी को प्रबंधित करने के लिए कोड जनरेट करने के कई अलग-अलग तरीके हैं, लेकिन समय के साथ दो बुनियादी तकनीकें उलझ गई हैं।

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

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

स्थानीय चर बाद के पैटर्न का पालन करते हैं; जब कोई विधि दर्ज की जाती है, तो इसके स्थानीय चर जीवंत हो जाते हैं। जब वह विधि किसी अन्य विधि को कॉल करती है, तो नई विधि के स्थानीय चर जीवंत हो जाते हैं। पहले तरीके के स्थानीय चर मृत होने से पहले वे मृत हो जाएंगे। स्थानीय चरों के साथ जुड़े स्टोरेज के जीवनकाल की शुरुआत और अंत के सापेक्ष आदेश पर समय से पहले काम किया जा सकता है।

इस कारण से, स्थानीय चर आमतौर पर "स्टैक" डेटा संरचना पर भंडारण के रूप में उत्पन्न होते हैं, क्योंकि एक स्टैक में संपत्ति होती है जिस पर पहली चीज को धक्का दिया जाता है, जो आखिरी चीज पॉप अप होती है।

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

तो चलो स्टैक के बारे में सोचते हैं। कई ऑपरेटिंग सिस्टम में आपको प्रति स्टैक एक स्टैक मिलता है और स्टैक एक निश्चित निश्चित आकार के लिए आवंटित किया जाता है। जब आप किसी विधि को कॉल करते हैं, तो स्टैक पर सामान धकेल दिया जाता है। यदि आप एक पॉइंटर को अपनी विधि से वापस लाते हैं, जैसा कि मूल पोस्टर यहाँ करता है, तो यह कुछ पूरी तरह से वैध मिलियन-बाइट मेमोरी ब्लॉक के बीच में एक पॉइंटर है। हमारे सादृश्य में, आप होटल से बाहर की जाँच करते हैं; जब आप ऐसा करते हैं, तो आप सबसे अधिक संख्या वाले कब्जे वाले कमरे से बाहर की जाँच करते हैं। यदि आपके बाद कोई और चेक नहीं करता है, और आप अवैध रूप से अपने कमरे में वापस जाते हैं, तो आपका सारा सामान इस विशेष होटल में रहने की गारंटी है

हम अस्थायी दुकानों के लिए ढेर का उपयोग करते हैं क्योंकि वे वास्तव में सस्ते और आसान हैं। स्थानीय लोगों के भंडारण के लिए स्टैक का उपयोग करने के लिए C ++ के कार्यान्वयन की आवश्यकता नहीं है; यह ढेर का उपयोग कर सकता है। यह नहीं है, क्योंकि यह कार्यक्रम को धीमा कर देगा।

सी ++ के कार्यान्वयन के लिए आवश्यक नहीं है कि आप कचरे को ढेर पर छोड़ दें, ताकि आप बाद में अवैध रूप से इसके लिए वापस आ सकें; यह कंपाइलर के लिए कोड उत्पन्न करने के लिए पूरी तरह से कानूनी है जो "रूम" में सब कुछ शून्य हो जाता है जिसे आपने अभी खाली किया था। यह फिर से नहीं है, क्योंकि यह महंगा होगा।

C ++ के कार्यान्वयन को यह सुनिश्चित करने की आवश्यकता नहीं है कि जब स्टैक तार्किक रूप से सिकुड़ता है, तो जो पते मान्य होते थे, वे अभी भी स्मृति में मैप किए जाते हैं। कार्यान्वयन को ऑपरेटिंग सिस्टम को बताने की अनुमति है "अब हम स्टैक के इस पृष्ठ का उपयोग कर रहे हैं। जब तक मैं अन्यथा नहीं कहता, तब तक एक अपवाद जारी करें जो प्रक्रिया को नष्ट कर देता है यदि कोई पहले से मान्य स्टैक पृष्ठ को छूता है"। फिर, कार्यान्वयन वास्तव में ऐसा नहीं करते हैं क्योंकि यह धीमा और अनावश्यक है।

इसके बजाय, कार्यान्वयन आपको गलतियाँ करने देते हैं और इससे दूर हो जाते हैं। सर्वाधिक समय। एक दिन तक कुछ सही मायने में भयानक हो जाता है और प्रक्रिया में विस्फोट हो जाता है।

यह समस्याग्रस्त है। बहुत सारे नियम हैं और उन्हें गलती से तोड़ना बहुत आसान है। मैं निश्चित रूप से कई बार है। और इससे भी बदतर, समस्या अक्सर सतहों की ही होती है जब स्मृति को भ्रष्टाचार के बाद नैनोसेकंड के अरबों भ्रष्ट होने का पता चलता है, जब यह पता लगाना बहुत मुश्किल होता है कि किसने इसे गड़बड़ किया।

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

आगे पढ़ने के लिए:

  • क्या होगा यदि C # ने संदर्भ लौटाने की अनुमति दी है? संयोगवश वह आज के ब्लॉग पोस्ट का विषय है:

    https://ericlippert.com/2011/06/23/ref-returns-and-ref-locals/

  • हम स्मृति को प्रबंधित करने के लिए ढेर का उपयोग क्यों करते हैं? क्या मूल्य प्रकार C # हमेशा स्टैक पर संग्रहीत होते हैं? वर्चुअल मेमोरी कैसे काम करती है? और C # मेमोरी मैनेजर कैसे काम करता है में कई और विषय। इन लेखों में से कई C ++ प्रोग्रामर के लिए जर्मेन भी हैं:

    https://ericlippert.com/tag/memory-management/


55
@ मंटू: दुर्भाग्य से यह ऐसा नहीं है कि ऑपरेटिंग सिस्टम एक चेतावनी को मोहिनी लगता है, इससे पहले कि वह वर्चुअल मेमोरी के एक पेज को डिमोलेट या डीललेट कर दे। यदि आप उस मेमोरी के साथ घूम रहे हैं, जब आपके पास यह नहीं है, तो ऑपरेटिंग सिस्टम पूरी तरह से पूरी प्रक्रिया को नीचे ले जाने के अधिकार में है जब आप एक डीलॉलेटेड पृष्ठ को स्पर्श करते हैं। बूम!
एरिक लिपर्ट

82
@ बाल: केवल सुरक्षित होटल ही ऐसा करते हैं। असुरक्षित होटल को प्रोग्रामिंग कीज़ पर समय बर्बाद न करने से औसत दर्जे का लाभ मिलता है।
अलेक्जेंडर टॉर्टलिंग ने

497
@cyberguijarro: यह C ++ मेमोरी सुरक्षित नहीं है बस एक तथ्य है। यह कुछ भी "कोसने" नहीं है। अगर मैंने कहा था, उदाहरण के लिए, "C ++ एक स्पष्ट, खतरनाक मेमोरी मॉडल के शीर्ष पर ढेर-से-जटिल, अत्यधिक जटिल विशेषताओं का एक भयंकर मिश्श्म है और मैं हर दिन आभारी हूं कि मैं अब अपनी खुद की एकता के लिए इसमें काम नहीं करता", वह C ++ को कोस रहा होगा। यह बताते हुए कि यह स्मृति सुरक्षित नहीं है, यह बता रहा है कि मूल पोस्टर इस मुद्दे को क्यों देख रहा है; यह प्रश्न का उत्तर दे रहा है, संपादकीय नहीं।
एरिक लिपर्ट

49
कड़ाई से बोलते हुए सादृश्य का उल्लेख करना चाहिए कि होटल में रिसेप्शनिस्ट आपके साथ चाबी लेने के लिए काफी खुश था। "ओह, क्या आपको बुरा लगा अगर मैं यह चाबी अपने साथ ले जाऊं?" "आगे बढ़ो। मुझे परवाह क्यों होगी? मैं केवल यहाँ काम करता हूँ"। जब तक आप इसका उपयोग करने की कोशिश नहीं करते, यह अवैध नहीं हो जाता।
philsquared

139
कृपया, कृपया कम से कम एक दिन पुस्तक लिखने पर विचार करें। मैं इसे तब भी खरीदूंगा जब यह केवल संशोधित और विस्तारित ब्लॉग पोस्ट का संग्रह था, और मुझे यकीन है कि बहुत सारे लोग होंगे। लेकिन विभिन्न प्रोग्रामिंग-संबंधित मामलों पर अपने मूल विचारों के साथ एक किताब एक महान पढ़ा जाएगा। मुझे पता है कि इसके लिए समय निकालना बहुत कठिन है, लेकिन कृपया इसे लिखने पर विचार करें।
दप्पल

275

क्या आप यहाँ क्या कर रहे हैं बस पढ़ने और उस स्मृति को लिख रहा है करने के लिए इस्तेमाल का पता होना a। अब जब आप बाहर हैं foo, तो यह कुछ यादृच्छिक मेमोरी क्षेत्र के लिए एक संकेतक है। यह सिर्फ इतना होता है कि आपके उदाहरण में, स्मृति क्षेत्र मौजूद है और कुछ भी नहीं इस समय इसका उपयोग कर रहा है। आप इसका उपयोग करना जारी रखते हुए कुछ भी नहीं तोड़ते हैं, और कुछ भी इसे अभी तक अधिलेखित नहीं किया है। इसलिए, 5अभी भी वहाँ है। एक वास्तविक कार्यक्रम में, उस मेमोरी को लगभग तुरंत उपयोग किया जाएगा और आप ऐसा करके कुछ तोड़ देंगे (हालांकि लक्षण बाद तक दिखाई नहीं दे सकते!)

जब आप वापस लौटते हैं foo, तो आप OS को बताते हैं कि अब आप उस मेमोरी का उपयोग नहीं कर रहे हैं और इसे किसी और चीज़ के लिए पुन: असाइन किया जा सकता है। यदि आप भाग्यशाली हैं और यह कभी भी आश्वस्त नहीं होता है, और ओएस आपको इसे फिर से उपयोग नहीं करता है, तो आप झूठ के साथ दूर हो जाएंगे। संभावना है कि आप उस पते के साथ जो कुछ भी समाप्त करते हैं, उस पर लिखना समाप्त कर देंगे।

अब अगर आप सोच रहे हैं कि संकलक शिकायत क्यों नहीं करता है, तो यह शायद इसलिए है क्योंकि fooअनुकूलन द्वारा समाप्त हो गया है। यह आमतौर पर आपको इस तरह की चीज के बारे में चेतावनी देगा। C मान लेता है कि आप जानते हैं कि आप क्या कर रहे हैं, और तकनीकी तौर पर आपने यहाँ स्कोप का उल्लंघन नहीं किया है (इसके aबाहर खुद का कोई संदर्भ नहीं है foo), केवल मेमोरी एक्सेस नियम, जो केवल एक त्रुटि के बजाय एक चेतावनी को ट्रिगर करता है।

संक्षेप में: यह आमतौर पर काम नहीं करेगा, लेकिन कभी-कभी संयोग से होगा।


151

क्योंकि भंडारण स्थान अभी तक पेट नहीं भरा था। उस व्यवहार पर भरोसा मत करो।


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

मैं शपथ ले सकता था कि मैंने बहुत पहले लिखा था, लेकिन यह हाल ही में पॉप अप हुआ और पाया कि मेरी प्रतिक्रिया नहीं थी। अब मुझे ऊपर आपके
गठबंधन का

1
Haha। फ्रांसिस बेकन, ब्रिटेन के सबसे महान निबंधकारों में से एक हैं, जिन पर कुछ लोगों को शक था कि उन्होंने शेक्सपियर के नाटकों को लिखा था, क्योंकि वे स्वीकार नहीं कर सकते कि देश का एक व्याकरण स्कूल का बच्चा, एक ग्लॉवर का बेटा, एक जीनियस हो सकता है। ऐसी है इंग्लिश क्लास सिस्टम। जीसस ने कहा, 'मैं सत्य हूं।' oregonstate.edu/instruct/phl302/texts/bacon/bacon_essays.html
Rob Kent

83

सभी उत्तरों के लिए थोड़ा अतिरिक्त:

अगर आप ऐसा कुछ करते हैं:

#include<stdio.h>
#include <stdlib.h>
int * foo(){
    int a = 5;
    return &a;
}
void boo(){
    int a = 7;

}
int main(){
    int * p = foo();
    boo();
    printf("%d\n",*p);
}

आउटपुट शायद होगा: 7

ऐसा इसलिए है क्योंकि फू () से लौटने के बाद स्टैक को मुक्त किया जाता है और फिर बू () द्वारा पुन: उपयोग किया जाता है। यदि आप निष्पादन योग्य को फिर से इकट्ठा करते हैं तो आप इसे स्पष्ट रूप से देखेंगे।


2
अंतर्निहित स्टैक सिद्धांत को समझने के लिए सरल, लेकिन महान उदाहरण है। बस एक परीक्षा जोड़ दें, "int a = 5;" फू में () "स्थिर int a = 5;" एक स्थिर चर के दायरे और जीवन समय को समझने के लिए इस्तेमाल किया जा सकता है।
नियंत्रण

15
-1 "के लिए शायद 7 होगा "। कंपाइलर बू में एनीगिस्टर कर सकता है। यह अनावश्यक होने के कारण इसे हटा सकते हैं। एक अच्छा मौका है कि * p 5 नहीं होगा , लेकिन इसका मतलब यह नहीं है कि कोई विशेष कारण है कि यह शायद 7 होगा
मैट

2
इसे अपरिभाषित व्यवहार कहा जाता है!
फ्रांसिस कुगलर

स्टैक का booपुन: उपयोग क्यों और कैसे किया जाता है foo? फंक्शन स्टैक एक-दूसरे से अलग नहीं होते हैं, मुझे भी विजुअल स्टूडियो 2015 पर इस कोड को चलाने के लिए कचरा मिलता है
ampawd

1
@ampawd यह लगभग एक साल पुराना है, लेकिन नहीं, "फ़ंक्शन स्टैक" एक दूसरे से अलग नहीं हैं। एक संपर्क में एक स्टैक है। वह संदर्भ मुख्य में प्रवेश करने के लिए अपने स्टैक का उपयोग करता है, फिर नीचे उतरता है foo(), अस्तित्व में होता है, फिर नीचे उतरता है boo()Foo()और Boo()दोनों एक ही स्थान पर स्टैक पॉइंटर के साथ प्रवेश करते हैं। हालाँकि, ऐसा व्यवहार नहीं है, जिस पर भरोसा किया जाना चाहिए। अन्य 'सामान' (जैसे रुकावट, या OS) कॉल के बीच स्टैक का उपयोग कर सकते हैं boo()और foo(), यह सामग्री को संशोधित कर सकते हैं ...
रस स्कुलट

71

C ++ में, आप किसी भी पते पर पहुंच सकते हैं , लेकिन इसका मतलब यह नहीं है कि आपको चाहिए । आप जिस पते पर पहुंच रहे हैं, वह अब मान्य नहीं है। यह काम करता है क्योंकि फू वापस आने के बाद स्मृति ने कुछ और नहीं बिखेरा, लेकिन यह कई परिस्थितियों में दुर्घटनाग्रस्त हो सकता है। Valgrind के साथ अपने कार्यक्रम का विश्लेषण करने की कोशिश करें , या यहां तक ​​कि इसे अनुकूलित करने के लिए संकलन करें, और देखें ...


5
आप शायद मतलब है कि आप किसी भी पते का उपयोग करने का प्रयास कर सकते हैं। क्योंकि अधिकांश ऑपरेटिंग सिस्टम आज किसी भी कार्यक्रम को किसी भी पते तक पहुंचने नहीं देंगे; पता स्थान की सुरक्षा के लिए बहुत सारे सुरक्षा उपाय हैं। यही कारण है कि वहाँ एक और LOADLIN.EXE नहीं होगा।
v010dya

66

आप कभी भी अमान्य स्मृति तक पहुँच कर C ++ अपवाद नहीं फेंकते। आप सिर्फ एक मनमाना स्मृति स्थान संदर्भित करने के सामान्य विचार का एक उदाहरण दे रहे हैं। मैं ऐसा ही कर सकता था:

unsigned int q = 123456;

*(double*)(q) = 1.2;

यहां मैं केवल 123456 को एक डबल के पते के रूप में मान रहा हूं और इसे लिखता हूं। कुछ भी हो सकता है:

  1. qवास्तव में वास्तव में एक डबल, जैसे की एक वैध पता हो सकता है double p; q = &p;
  2. q आवंटित स्मृति के अंदर कहीं बिंदु हो सकता है और मैं वहां केवल 8 बाइट को अधिलेखित कर सकता हूं।
  3. q आबंटित मेमोरी के बाहर के अंक और ऑपरेटिंग सिस्टम की मेमोरी मैनेजर मेरे प्रोग्राम को एक सेगमेंटेशन फॉल्ट सिग्नल भेजता है, जिससे रनटाइम इसे समाप्त कर देता है।
  4. आप लॉटरी जीतते हैं।

जिस तरह से आप इसे सेट करते हैं यह थोड़ा अधिक उचित है कि लौटा हुआ पता मेमोरी के एक वैध क्षेत्र में इंगित करता है, क्योंकि यह संभवतः स्टैक से थोड़ा आगे होगा, लेकिन यह अभी भी एक अमान्य स्थान है जिसे आप एक्सेस नहीं कर सकते हैं नियतात्मक फैशन।

कोई भी स्वचालित रूप से सामान्य प्रोग्राम निष्पादन के दौरान आपके लिए मेमोरी पतों की शब्दार्थ वैधता की जाँच नहीं करेगा। हालाँकि, एक मेमोरी डीबगर जैसे कि valgrindखुशी से ऐसा करेगा, इसलिए आपको अपना प्रोग्राम इसके माध्यम से चलाना चाहिए और त्रुटियों को देखना चाहिए।


9
मैं अभी एक प्रोग्राम लिखने जा रहा हूँ जो इस प्रोग्राम को चालू रखता है4) I win the lottery
Aidiakapi

28

क्या आपने आशावादी सक्षम के साथ अपने कार्यक्रम को संकलित किया है? foo()समारोह काफी सरल है और inlined गया हो सकता है या जिसके परिणामस्वरूप कोड में बदल दिया।

लेकिन मैं मार्क बी से सहमत हूं कि परिणामी व्यवहार अपरिभाषित है।


यही मेरी शर्त है। ऑप्टिमाइज़र ने फ़ंक्शन कॉल को डंप किया।
एरिक एरोनिटी

9
यह आवश्यक नहीं है। चूंकि फू () के बाद कोई नया फ़ंक्शन नहीं कहा जाता है, इसलिए फ़ंक्शन स्थानीय स्टैक फ़्रेम को अभी तक अधिलेखित नहीं किया गया है। फू () के बाद एक और फंक्शन इनवोकेशन जोड़ें, और 5इसे बदल दिया जाएगा ...
टॉमस

मैंने जीसीसी 4.8 के साथ प्रिंटफ के साथ कॉउट की जगह (और स्टीडियो सहित) कार्यक्रम चलाया। सही ढंग से "चेतावनी: स्थानीय चर का पता 'एक' लौटा [-वर्थ-लोकल-एड्र]]। 58 ऑप्टिमाइज़ेशन के साथ आउटपुट और -ओ 3 के साथ 08। अजीब तरह से पी का एक पता होता है, भले ही इसका मूल्य 0. है। मुझे पता के रूप में NULL (0) की उम्मीद थी।
केविनफ

22

आपकी समस्या का स्कोप से कोई लेना-देना नहीं है । आपके द्वारा दिखाए गए कोड mainमें, फ़ंक्शन फ़ंक्शन में नामों को नहीं देखता है foo, इसलिए आप इस नाम के aसाथ सीधे बाहर फू में नहीं पहुंच सकते हैं ।foo

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


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

@ मिचेल काजोलिंग: ज़रूर! लोग एक बार में कुछ गंदा काम करना पसंद करते हैं;)
चांग पेंग

17

आप केवल एक स्मृति पता लौटा रहे हैं, इसकी अनुमति है लेकिन शायद एक त्रुटि है।

हां, यदि आप उस मेमोरी एड्रेस को डिरेल करने की कोशिश करते हैं तो आपके पास अपरिभाषित व्यवहार होगा।

int * ref () {

 int tmp = 100;
 return &tmp;
}

int main () {

 int * a = ref();
 //Up until this point there is defined results
 //You can even print the address returned
 // but yes probably a bug

 cout << *a << endl;//Undefined results
}

मैं असहमत हूं: के सामने एक समस्या है cout*aअनअलोकेटेड (मुक्त) स्मृति के लिए अंक। यहां तक ​​कि अगर आप इसे कम नहीं करते हैं, तो यह अभी भी खतरनाक है (और संभावित फर्जी)।
--रॉन

@ereOn: मैंने अधिक स्पष्ट किया कि मुझे समस्या से क्या मतलब है, लेकिन यह मान्य c ++ कोड के संदर्भ में खतरनाक नहीं है। लेकिन यह संभावना के संदर्भ में खतरनाक है कि उपयोगकर्ता ने गलती की है और कुछ बुरा करेगा। हो सकता है कि उदाहरण के लिए आप यह देखना चाहते हैं कि स्टैक कैसे बढ़ता है, और आप केवल पते के मूल्य के बारे में परवाह करते हैं और इसे कभी नहीं करेंगे।
ब्रायन आर। बॉन्डी

17

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


17

यह व्यवहार अपरिभाषित है, जैसा कि एलेक्स ने बताया - वास्तव में, अधिकांश संकलक ऐसा करने के खिलाफ चेतावनी देंगे, क्योंकि यह क्रैश करने का एक आसान तरीका है।

डरावना व्यवहार आप कर रहे हैं की तरह का एक उदाहरण के लिए संभावना प्राप्त करने के लिए, इस नमूने का प्रयास करें:

int *a()
{
   int x = 5;
   return &x;
}

void b( int *c )
{
   int y = 29;
   *c = 123;
   cout << "y=" << y << endl;
}

int main()
{
   b( a() );
   return 0;
}

यह "y = 123" प्रिंट करता है, लेकिन आपके परिणाम भिन्न हो सकते हैं (वास्तव में!)। आपका पॉइंटर अन्य, असंबंधित स्थानीय चर को क्लोब कर रहा है।


17

सभी चेतावनियों पर ध्यान दें। केवल त्रुटियों को हल न करें।
GCC इस वार्निंग को दिखाती है

चेतावनी: स्थानीय चर 'a' का पता लौटा

यह C ++ की शक्ति है। आपको स्मृति की परवाह करनी चाहिए। -Werrorध्वज के साथ , यह चेतावनी एक त्रुटि बन जाती है और अब आपको इसे डीबग करना होगा।


16

यह काम करता है क्योंकि स्टैक को बदल नहीं दिया गया है (अभी तक) क्योंकि वहां डाल दिया गया था। कुछ अन्य फ़ंक्शंस (जो अन्य फ़ंक्शंस पर भी कॉल कर रहे हैं) को aफिर से एक्सेस करने से पहले कॉल करें और आप शायद अब और भाग्यशाली नहीं होंगे; ;-)


15

आपने वास्तव में अपरिभाषित व्यवहार का आह्वान किया।

अस्थायी कार्यों का पता लौटाना, लेकिन जब एक समारोह के अंत में अस्थायी लोगों को नष्ट कर दिया जाता है, तो उन तक पहुँचने के परिणाम असमान हो जाएंगे।

इसलिए आपने संशोधित नहीं किया a, बल्कि मेमोरी लोकेशन जहां aएक बार थी। यह अंतर दुर्घटनाग्रस्त और दुर्घटनाग्रस्त न होने के अंतर के समान है।


13

ठेठ संकलक कार्यान्वयन में, आप कोड के बारे में सोच सकते हैं "एड्रेस के साथ मेमोरी ब्लॉक के मूल्य का प्रिंट आउट करें जो कि " द्वारा कब्जा किया जाता था । इसके अलावा, यदि आप किसी ऐसे फ़ंक्शन में एक नया फ़ंक्शन इनवोकेशन जोड़ते हैं जो एक स्थानीय को स्थिर करता है तो intयह एक अच्छा मौका है कि a(या स्मृति पते का aउपयोग करने के लिए इंगित करता है) का मान बदलता है। ऐसा इसलिए होता है क्योंकि स्टैक को अलग डेटा वाले नए फ्रेम के साथ ओवरराइट किया जाएगा।

हालांकि, यह अपरिभाषित व्यवहार है और आपको काम करने के लिए इस पर भरोसा नहीं करना चाहिए!


3
"प्रिंट आउट उस पते से स्मृति ब्लॉक के मूल्य प्रयोग की जाने वाली काफी नहीं सही है एक के कब्जे में"। इससे यह लगता है कि उसके कोड में कुछ अच्छी तरह से परिभाषित अर्थ है, जो ऐसा नहीं है। आप सही कह रहे हैं कि यह संभवतया है कि अधिकांश कंपाइलर इसे कैसे लागू करेंगे, हालांकि।
ब्रेनन विंसेंट

@BrennanVincent: भंडारण द्वारा कब्जा कर लिया गया था a, जबकि सूचक का पता आयोजित किया a। हालाँकि, मानक को यह आवश्यकता नहीं है कि कार्यान्वयन उनके लक्ष्य के जीवनकाल समाप्त होने के बाद पतों के व्यवहार को परिभाषित करते हैं, यह यह भी स्वीकार करता है कि कुछ प्लेटफार्मों पर UB को पर्यावरण की विशेषता वाले प्रलेखित तरीके से संसाधित किया जाता है। हालांकि एक स्थानीय चर का पता आम तौर पर बहुत अधिक उपयोग नहीं होगा, क्योंकि यह दायरे से बाहर जाने के बाद, कुछ अन्य प्रकार के पते अभी भी अपने संबंधित लक्ष्यों के जीवनकाल के बाद सार्थक हो सकते हैं।
सुपरकैट

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

13

यह कर सकते हैं, क्योंकि aएक चर है जो अस्थायी रूप से अपने दायरे ( fooकार्य) के जीवनकाल के लिए आवंटित किया गया है । आपके द्वारा fooमेमोरी से लौटने के बाद फ्री है और ओवरराइट किया जा सकता है।

आप जो कर रहे हैं वह अपरिभाषित व्यवहार के रूप में वर्णित है । परिणाम की भविष्यवाणी नहीं की जा सकती।


11

सही (?) कंसोल आउटपुट वाली चीजें नाटकीय रूप से बदल सकती हैं यदि आप :: प्रिंटफ का उपयोग करते हैं लेकिन कूट नहीं। आप नीचे कोड के भीतर डिबगर के साथ खेल सकते हैं (x86, 32-बिट, MSVisual स्टूडियो पर परीक्षण):

char* foo() 
{
  char buf[10];
  ::strcpy(buf, "TEST”);
  return buf;
}

int main() 
{
  char* s = foo();    //place breakpoint & check 's' varialbe here
  ::printf("%s\n", s); 
}

4

किसी फ़ंक्शन से लौटने के बाद, सभी पहचानकर्ता स्मृति स्थान में रखे गए मानों के बजाय नष्ट हो जाते हैं और हम पहचानकर्ता के बिना मानों का पता नहीं लगा सकते हैं। लेकिन उस स्थान में अभी भी पिछले फ़ंक्शन द्वारा संग्रहीत मान शामिल हैं।

तो, यहाँ फ़ंक्शन foo()का पता लौट रहा है aऔर aउसका पता वापस करने के बाद नष्ट हो गया है। और आप उस लौटे पते के माध्यम से संशोधित मूल्य तक पहुंच सकते हैं।

मुझे एक वास्तविक दुनिया का उदाहरण लेने दें:

मान लीजिए कि कोई आदमी किसी स्थान पर पैसे छिपाता है और आपको स्थान बताता है। कुछ समय बाद, जिस आदमी ने आपको पैसे का स्थान बताया था, वह मर गया। लेकिन फिर भी आपके पास उस छिपे हुए पैसे की पहुंच है।


3

यह मेमोरी पतों का उपयोग करने का 'गंदा' तरीका है। जब आप एक पता (पॉइंटर) लौटाते हैं, तो आप यह नहीं जानते कि यह किसी फ़ंक्शन के स्थानीय दायरे से संबंधित है या नहीं। यह सिर्फ एक पता है। अब जब आपने 'foo' फंक्शन शुरू किया था, तो आपके एप्लिकेशन (प्रक्रिया) का पता (मेमोरी लोकेशन) पहले से ही वहां (सुरक्षित रूप से, कम से कम) एड्रेस करने योग्य मेमोरी में पहले से ही आबंटित था। 'फू' फ़ंक्शन वापस आने के बाद, 'ए' के ​​पते को 'गंदा' माना जा सकता है, लेकिन यह वहाँ है, न तो साफ किया जाता है, और न ही कार्यक्रम के अन्य भाग में (इस विशिष्ट मामले में कम से कम) भावों द्वारा परेशान / संशोधित किया जाता है। AC / C ++ कंपाइलर आपको इस तरह की 'गंदी' पहुंच से नहीं रोकता है (यदि आप परवाह करते हैं तो आपको चेतावनी दे सकते हैं)।


1

आपका कोड बहुत जोखिम भरा है। आप एक स्थानीय चर बना रहे हैं (जिसे फ़ंक्शन समाप्त होने के बाद नष्ट माना जाता है) और आप उस चर के नष्ट होने के बाद उस चर की स्मृति का पता वापस करते हैं।

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

इसका मतलब यह है कि आप एक बहुत बुरा काम कर रहे हैं, क्योंकि आप एक पॉइंटर पॉइच करने के लिए मेमोरी एड्रेस पास कर रहे हैं, यह बिल्कुल भी विश्वसनीय नहीं है।

इसके बजाय इस उदाहरण पर विचार करें, और इसका परीक्षण करें:

int * foo()
{
   int *x = new int;
   *x = 5;
   return x;
}

int main()
{
    int* p = foo();
    std::cout << *p << "\n"; //better to put a new-line in the output, IMO
    *p = 8;
    std::cout << *p;
    delete p;
    return 0;
}

अपने उदाहरण के विपरीत, इस उदाहरण के साथ आप हैं:

  • एक स्थानीय समारोह में int के लिए स्मृति आवंटित करना
  • वह मेमोरी पता तब भी मान्य होता है जब फ़ंक्शन समाप्त हो जाता है, (यह किसी के द्वारा हटाया नहीं जाता है)
  • मेमोरी एड्रेस भरोसेमंद है (यह मेमोरी ब्लॉक फ्री नहीं माना जाता है, इसलिए इसे तब तक ओवरराइड नहीं किया जाएगा जब तक इसे डिलीट न कर दिया जाए)
  • उपयोग नहीं होने पर मेमोरी एड्रेस को डिलीट कर देना चाहिए। (कार्यक्रम के अंत में डिलीट देखें)

क्या आपने कुछ जोड़ा जो पहले से मौजूद उत्तरों से कवर नहीं है? और कृपया कच्चे पॉइंटर्स / का उपयोग न करें new
लाइटनेस दौड़ ऑर्बिट में

1
पूछने वाले ने कच्चे पॉइंटर्स का इस्तेमाल किया। मैंने एक उदाहरण दिया जो कि उदाहरण के रूप में परिलक्षित होता है जो उसने असत्य सूचक और भरोसेमंद के बीच अंतर देखने के लिए किया था। वास्तव में मेरे जैसा ही एक और उत्तर है, लेकिन यह स्ट्रिच विच का उपयोग करता है, IMHO, मेरे उदाहरण की तुलना में एक नौसिखिए कोडर के लिए कम स्पष्ट हो सकता है जो नए का उपयोग करता है।
नोबुन

उन्होंने इस्तेमाल नहीं किया new। आप उन्हें उपयोग करना सिखा रहे हैं new। लेकिन आपको उपयोग नहीं करना चाहिए new
लाइटनेस रेस ऑर्बिट में

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

मैने ये कब कहा? नहीं, संदर्भित संसाधन के स्वामित्व को ठीक से इंगित करने के लिए स्मार्ट पॉइंटर्स का उपयोग करना बेहतर है। new2019 में उपयोग न करें (जब तक कि आप लाइब्रेरी कोड नहीं लिख रहे हों) और नए लोगों को ऐसा करना न सिखाएं! चीयर्स।
लाइटनेस दौड़ ऑर्बिट में
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.