सी में, ब्रेसिज़ एक स्टैक फ्रेम के रूप में कार्य करते हैं?


153

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

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

खंड के dदौरान मेमोरी लेना होगा code that takes a while?


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

जवाबों:


83

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

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

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

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


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

54
C ++ में, किसी वस्तु के विध्वंसक को उसके दायरे के अंत में बुलाया जाता है। चाहे स्मृति पुनः प्राप्त हो, एक कार्यान्वयन-विशिष्ट मुद्दा है।
क्रिस्टोफर जॉनसन

8
@ pm100: विध्वंसक कहलाएंगे। यह स्मृति के बारे में कुछ नहीं कहता है कि उन वस्तुओं ने कब्जा कर लिया।
डोनल फेलो

9
सी मानक निर्दिष्ट करता है कि ब्लॉक में घोषित स्वचालित चर का जीवनकाल केवल तब तक विस्तारित होता है जब तक कि ब्लॉक समाप्त नहीं हो जाता। तो अनिवार्य रूप से उन स्वत: चर है ब्लॉक के अंत में "नष्ट" मिलता है।
CAF

3
@KristopherJohnson: यदि किसी विधि में दो अलग-अलग ब्लॉक थे, जिनमें से प्रत्येक में 1Kbyte सरणी घोषित की गई थी, और तीसरा ब्लॉक जिसे नेस्टेड विधि कहा जाता था, एक संकलक दोनों सरणियों के लिए एक ही मेमोरी का उपयोग करने के लिए स्वतंत्र होगा, और / या सरणी रखने के लिए। स्टैक के उथले भाग पर और नेस्टेड विधि को बुलाते हुए स्टैक पॉइंटर को ऊपर ले जाएं। इस तरह के व्यवहार को 2K से फंकशन कॉल के लिए आवश्यक स्टैक डेप्थ को कम किया जा सकता है।
सुपरकैट

39

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

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

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

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

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

   return;
}

(दूसरे शब्दों में: संकलक को व्यवहार करने की अनुमति दी जाती है d, भले ही व्यवहार में न हो?)।

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

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


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

20

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

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

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

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

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

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


6

यह कार्यान्वयन पर निर्भर है। मैंने एक छोटा सा प्रोग्राम लिखा था जो यह बताता है कि gcc 4.3.4 क्या करता है, और यह फंक्शन के प्रारंभ में एक ही बार में स्टैक स्पेस को आवंटित करता है। आप विधानसभा का परीक्षण कर सकते हैं कि -S ध्वज का उपयोग करके gcc का उत्पादन करता है।


3

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

संपादित करें: क्रिस्टोफर जॉनसन (और सिमोन और डैनियल) सही हैं , और मेरी प्रारंभिक प्रतिक्रिया गलत थी । Gcc के साथ 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

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

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

" जैसे ही सरणी नाम का दायरा समाप्त होता है, वैरिएबल-लेंथ एरे के लिए स्पेस डील हो जाता है ।"


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

2

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


1

आपका वैरिएबल dआमतौर पर स्टैक से पॉपअप नहीं होता है। घुंघराले ब्रेसिज़ एक स्टैक फ्रेम को निरूपित नहीं करते हैं। अन्यथा, आप ऐसा कुछ नहीं कर पाएंगे:

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

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

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

अद्यतन: यहाँ सी कल्पना का क्या कहना है। स्वचालित भंडारण अवधि वाली वस्तुओं के बारे में (खंड 6.4.2):

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

समान खंड "आजीवन" शब्द को (जोर मेरा) के रूप में परिभाषित करता है:

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

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

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

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


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

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

0

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


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

0

मानक पर पहले से ही बहुत कुछ जानकारी दी गई है जो यह दर्शाता है कि यह वास्तव में कार्यान्वयन विशिष्ट है।

तो, एक प्रयोग रुचि का हो सकता है। यदि हम निम्नलिखित कोड आज़माएँ:

#include <stdio.h>
int main() {
    int* x;
    int* y;
    {
        int a;
        x = &a;
        printf("%p\n", (void*) x);
    }
    {
        int b;
        y = &b;
        printf("%p\n", (void*) y);
    }
}

जीसीसी का उपयोग करके हम यहां एक ही पते पर दो बार प्राप्त करते हैं: कोलीरो

लेकिन अगर हम निम्नलिखित कोड का प्रयास करें:

#include <stdio.h>
int main() {
    int* x;
    int* y;
    {
        int a;
        x = &a;
    }
    {
        int b;
        y = &b;
    }
    printf("%p\n", (void*) x);
    printf("%p\n", (void*) y);
}

जीसीसी का उपयोग करके हम यहां दो अलग-अलग पते प्राप्त करते हैं: कोलीरो

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

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.