2010-05-03 16 views
148

यदि मैं घुंघराले ब्रेसिज़ के एक नए सेट के भीतर एक चर बना देता हूं, तो क्या यह चर बंद होने पर ढेर से निकलता है, या यह फ़ंक्शन के अंत तक बाहर निकलता है? उदाहरण के लिए:सी में, ब्रेसिज़ एक स्टैक फ्रेम के रूप में कार्य करते हैं?

void foo() { 
    int c[100]; 
    { 
     int d[200]; 
    } 
    //code that takes a while 
    return; 
} 

dcode that takes a while अनुभाग के दौरान स्मृति ले जाएगा?

+7

क्या आपका मतलब है (1) मानक के अनुसार, (2) कार्यान्वयन के बीच सार्वभौमिक अभ्यास, या (3) कार्यान्वयन के बीच सामान्य अभ्यास? –

उत्तर

77

नहीं, ब्रेसिज़ एक स्टैक फ्रेम के रूप में कार्य नहीं करते हैं। सी में, ब्रेसिज़ केवल नामकरण के दायरे को दर्शाते हैं, लेकिन कुछ भी नष्ट नहीं होता है और न ही नियंत्रण से बाहर निकलने पर कुछ भी ढेर से निकलता है।

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

यह भी ध्यान रखें कि स्थानीय चर किसी भी स्टैक स्पेस का उपयोग नहीं कर सकते हैं: वे सीपीयू रजिस्टरों में या किसी अन्य सहायक भंडारण स्थान में हो सकते हैं, या पूरी तरह अनुकूलित हो सकते हैं।

तो, d सरणी, सिद्धांत में, पूरे कार्य के लिए स्मृति का उपभोग कर सकता है। हालांकि, कंपाइलर इसे अनुकूलित कर सकता है, या अन्य मेमोरी को अन्य स्थानीय चर के साथ साझा कर सकता है जिसका उपयोग जीवनकाल ओवरलैप नहीं होता है।

+9

क्या कार्यान्वयन-विशिष्ट नहीं है? – avakar

+1

क्या यह सी ++/अन्य भाषाओं में अलग-अलग काम करता है? –

+52

सी ++ में, किसी ऑब्जेक्ट के विनाशक को इसके दायरे के अंत में बुलाया जाता है। चाहे स्मृति पुनः प्राप्त हो, कार्यान्वयन-विशिष्ट समस्या है। सी ++ में –

0

मेरा मानना ​​है कि यह गुंजाइश से बाहर है, लेकिन फ़ंक्शन लौटने तक स्टैक से पॉप-एड नहीं है। तो यह तब तक स्टैक पर स्मृति ले जाएगा जब तक कि कार्य पूरा नहीं हो जाता है, लेकिन पहले बंद घुंघराले ब्रेस के डाउनस्ट्रीम तक पहुंच योग्य नहीं है।

+3

कोई गारंटी नहीं है। एक बार जब स्कोप बंद हो जाता है तो संकलक अब उस स्मृति का ट्रैक नहीं रखता है (या कम से कम इसकी आवश्यकता नहीं है ...) और इसका पुन: उपयोग कर सकते हैं।यही कारण है कि पूर्व में स्कोप वैरिएबल से बाहर की गई स्मृति को छूना अपरिभाषित व्यवहार है। नाक राक्षसों और इसी तरह की चेतावनियों से सावधान रहें। – dmckee

6

यह कार्यान्वयन निर्भर है। मैंने यह जांचने के लिए एक छोटा कार्यक्रम लिखा है कि जीसीसी 4.3.4 क्या करता है, और यह फ़ंक्शन की शुरुआत में सभी स्टैक स्पेस को आवंटित करता है। आप असेंबली की जांच कर सकते हैं कि जीसीसी -एस ध्वज का उपयोग करता है।

3

नहीं, डी [] दिनचर्या के शेष के लिए ढेर पर हो। लेकिन alloca() अलग है।

संपादित करें: क्रिस्टोफ़र जॉनसन (और साइमन और डैनियल) सही हैं, और मेरी प्रारंभिक प्रतिक्रिया गलत था। जीसीसी 4.3.4.on CYGWIN, कोड के साथ:

void foo(int[]); 
void bar(void); 
void foobar(int); 

void foobar(int flag) { 
    if (flag) { 
     int big[100000000]; 
     foo(big); 
    } 
    bar(); 
} 

देता है:

_foobar: 
    pushl %ebp 
    movl %esp, %ebp 
    movl $400000008, %eax 
    call __alloca 
    cmpl $0, 8(%ebp) 
    je  L2 
    leal -400000000(%ebp), %eax 
    movl %eax, (%esp) 
    call _foo 
L2: 
    call _bar 
    leave 
    ret 

लाइव और जानने के! और एक त्वरित परीक्षण यह दिखाता है कि एंड्रीटी कई आवंटन के बारे में भी सही है।

बाद में जोड़ा गया: उपर्युक्त परीक्षण gcc documentation बिल्कुल सही नहीं है।सालों के लिए यह कहा है (जोर जोड़ा):

"एक चर लंबाई सरणी के लिए अंतरिक्ष पुनः आवंटित की जाती जैसे हीसमाप्त होता है सरणी नाम के गुंजाइश के रूप में है।"

+0

ऑप्टिमाइज़ेशन अक्षम के साथ संकलन आवश्यक रूप से आपको दिखाता है कि आपको अनुकूलित कोड में क्या मिलेगा। इस मामले में, व्यवहार वही है (फ़ंक्शन की शुरुआत में आवंटित करें, और फ़ंक्शन छोड़ते समय केवल निःशुल्क): https://godbolt.org/g/M112AQ। लेकिन गैर-सिग्विन जीसीसी एक 'एलोका' समारोह नहीं कहता है। मैं वास्तव में हैरान हूं कि साइगविन जीसीसी ऐसा करेगा। यह एक परिवर्तनीय-लंबाई सरणी भी नहीं है, इसलिए आईडीके आप इसे क्यों लाते हैं। –

18

आपका प्रश्न स्पष्ट रूप से उत्तर देने के लिए पर्याप्त स्पष्ट नहीं है।

एक तरफ, कंपाइलर्स आमतौर पर नेस्टेड ब्लॉक स्कोप्स के लिए कोई स्थानीय मेमोरी आवंटन-डेलोकेशन नहीं करते हैं। स्थानीय मेमोरी आमतौर पर फ़ंक्शन एंट्री पर केवल एक बार आवंटित की जाती है और फ़ंक्शन निकास पर रिलीज़ होती है।

दूसरी ओर, जब स्थानीय ऑब्जेक्ट का जीवनकाल समाप्त होता है, तो उस ऑब्जेक्ट पर कब्जा कर लिया गया स्मृति बाद में किसी अन्य स्थानीय ऑब्जेक्ट के लिए पुन: उपयोग किया जा सकता है। उदाहरण के लिए, इस कोड में

void foo() 
{ 
    { 
    int d[100]; 
    } 
    { 
    double e[20]; 
    } 
} 

दोनों सरणियों आमतौर पर एक ही स्मृति क्षेत्र पर कब्जा है, इसका अर्थ स्थानीय भंडारण की कुल राशि समारोह foo की जरूरत है जो कुछ भी, दो की सबसे बड़ा सरणियों के लिए आवश्यक है नहीं है कि दोनों एक ही समय में उनके लिए।

क्या बाद में d आपके प्रश्न के संदर्भ में कार्य के अंत तक स्मृति पर कब्जा करने के लिए जारी है, यह तय करने के लिए है।

1

आपके चर d आमतौर पर ढेर से बाहर नहीं निकलता है। घुंघराले ब्रेसिज़ एक ढेर फ्रेम को इंगित नहीं करते हैं। अन्यथा, आप इस तरह कुछ करने के लिए सक्षम नहीं होगा:

char var = getch(); 
    { 
     char next_var = var + 1; 
     use_variable(next_char); 
    } 

तो घुंघराले ब्रेसिज़ एक सच्चे ढेर धक्का/पॉप (एक समारोह कहेंगे की तरह) के कारण होता है, तो ऊपर दिए गए कोड को संकलित नहीं करेंगे, क्योंकि अंदर कोड ब्रेसिज़ चर var तक पहुंचने में सक्षम नहीं होंगे जो ब्रेसिज़ के बाहर रहता है (जैसे उप-फ़ंक्शन कॉलिंग फ़ंक्शन में सीधे चर नहीं पहुंच सकता)। हम जानते हैं कि यह मामला नहीं है।

घुंघराले ब्रेसिज़ का उपयोग बस स्कॉइंग के लिए किया जाता है। कंपाइलर संलग्नक ब्रेसिज़ के बाहर से "आंतरिक" चर के किसी भी पहुंच को अमान्य के रूप में मानता है, और यह उस स्मृति को किसी अन्य चीज़ के लिए फिर से उपयोग कर सकता है (यह कार्यान्वयन-निर्भर है)। हालांकि, जब तक संलग्न कार्य वापस नहीं आ जाता है तब तक इसे स्टैक से बाहर नहीं किया जा सकता है।

अद्यतन: यहां C spec क्या कहना है। स्वत: भंडारण अवधि के साथ वस्तुओं के संबंध में (खंड 6.4.2):

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

एक वस्तु का जीवन प्रोग्राम निष्पादन का हिस्सा होता है:

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

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

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

आप यह सुनिश्चित करें कि सरणी d नहीं रह गया है, जबकि अपनी स्मृति कोड चल रहा है ऊपर खा रहा है चाहते हैं, तो आपको एक अलग समारोह में घुंघराले ब्रेसिज़ में कोड परिवर्तित कर सकते हैं या तो या स्पष्ट रूप से malloc और free स्मृति के बजाय स्वत: संग्रहण का उपयोग ।

+1

_ "अगर घुंघराले ब्रेसिज़ ने स्टैक पुश/पॉप का कारण बनता है, तो उपर्युक्त कोड संकलित नहीं होगा क्योंकि ब्रेसिज़ के अंदर कोड ब्रेसिज़ के बाहर रहने वाले परिवर्तनीय var तक पहुंचने में सक्षम नहीं होगा" _ - यह बस सच नहीं है। कंपाइलर हमेशा स्टैक/फ्रेम सूचक से दूरी याद कर सकते हैं, और बाहरी चर के संदर्भ में इसका उपयोग कर सकते हैं। इसके अलावा, यूसुफ के घुंघराले ब्रेसिज़ के उदाहरण के लिए उत्तर देखें जो _do_ एक स्टैक पुश/पॉप का कारण बनता है। – george

+0

@ जॉर्ज- आपके द्वारा वर्णित व्यवहार, साथ ही जोसेफ का उदाहरण, आपके द्वारा उपयोग किए जा रहे कंपाइलर और प्लेटफॉर्म पर निर्भर है। उदाहरण के लिए, एक एमआईपीएस लक्ष्य के लिए एक ही कोड को संकलित करने से पूरी तरह अलग परिणाम मिलते हैं। मैं पूरी तरह से सी spec के दृष्टिकोण से बात कर रहा था (चूंकि ओपी ने एक कंपाइलर या लक्ष्य निर्दिष्ट नहीं किया था)। मैं जवाब संपादित कर दूंगा और अधिक विशिष्टताओं को जोड़ूंगा। – bta

1

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

36

वह समय जिसके दौरान चर है, वास्तव में मेमोरी लेना स्पष्ट रूप से संकलक-निर्भर है (और कई कंपाइलर स्टैक पॉइंटर को समायोजित नहीं करते हैं जब आंतरिक ब्लॉक प्रवेश किए जाते हैं और कार्यों में बाहर निकलते हैं)।

void foo() { 
    int c[100]; 
    int *p; 

    { 
     int d[200]; 
     p = d; 
    } 

    /* Can I access p[0] here? */ 

    return; 
} 

(में दूसरे शब्दों:

हालांकि, एक निकट से संबंधित है, लेकिन संभवतः अधिक दिलचस्प सवाल कार्यक्रम भीतरी गुंजाइश (लेकिन युक्त समारोह के अंदर), यानी बाहर है कि आंतरिक वस्तु का उपयोग करने की अनुमति है या नहीं है : संकलक को d को हटाने के लिए अनुमति है, भले ही अभ्यास में अधिकांश न करें?)।

जवाब यह है कि संकलक d पुनःआवंटन करने की अनुमति दी है, और तक पहुँचने p[0] जहां टिप्पणी को इंगित करता है अपरिभाषित व्यवहार (कार्यक्रम भीतरी दायरे के बाहर भीतरी वस्तु देखने की अनुमति नहीं है) है। सी मानक का प्रासंगिक हिस्सा 6.2 है।4p5:

इस तरह के एक वस्तु के लिए [एक है कि स्वत: भंडारण अवधि] कि करता है एक चर लंबाई सरणी प्रकार नहीं, अपने जीवनकाल जिसके साथ यह निष्पादन तक जुड़ा हुआ है ब्लॉक में प्रवेश तक फैली हुई है उस ब्लॉक का किसी भी तरह में समाप्त होता है। (एक संलग्न ब्लॉक दर्ज करना या फ़ंक्शन को निलंबित करना, लेकिन वर्तमान ब्लॉक का निष्पादन समाप्त नहीं होता है।) यदि ब्लॉक दोबारा दर्ज किया गया है, तो ऑब्जेक्ट का एक नया उदाहरण हर बार बनाया जाता है। ऑब्जेक्ट का प्रारंभिक मान अनिश्चित है। यदि ऑब्जेक्ट के लिए निर्दिष्ट प्रारंभ होता है, तो यह होता है जब प्रत्येक बार घोषणा ब्लॉक के निष्पादन में पहुंच जाती है; अन्यथा, मान प्रत्येक बार घोषणा समाप्त होने पर अनिश्चित हो जाता है।

+0

उच्च शिक्षा वाली भाषाओं का उपयोग करने के वर्षों के बाद सी और सी ++ में कोई स्कोप और मेमोरी कैसे काम करता है, मुझे यह उत्तर मिलता है कि मुझे यह उत्तर स्वीकार किए गए एक से अधिक सटीक और उपयोगी लगता है। – Chris

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