फंक्शन-लेवल स्टैटिक वेरिएबल्स को कब आवंटित / इनिशियलाइज़ किया जाता है?


89

मुझे पूरा विश्वास है कि वैश्विक रूप से घोषित चरों को कार्यक्रम शुरू होने के समय आवंटित किया जाता है (और यदि शुरू में लागू होता है)।

int globalgarbage;
unsigned int anumber = 42;

लेकिन एक समारोह में स्थिर लोगों के बारे में क्या?

void doSomething()
{
  static bool globalish = true;
  // ...
}

कब globalishआवंटित करने के लिए जगह है ? मैं अनुमान लगा रहा हूं कि कार्यक्रम कब शुरू होगा। लेकिन क्या यह आरंभ में भी मिलता है? या जब doSomething()इसे पहली बार कहा जाता है, तो इसे आरंभीकृत किया जाता है?

जवाबों:


91

मैं इसके बारे में उत्सुक था इसलिए मैंने निम्नलिखित परीक्षण कार्यक्रम लिखा और इसे g ++ संस्करण 4.1.2 के साथ संकलित किया।

include <iostream>
#include <string>

using namespace std;

class test
{
public:
        test(const char *name)
                : _name(name)
        {
                cout << _name << " created" << endl;
        }

        ~test()
        {
                cout << _name << " destroyed" << endl;
        }

        string _name;
};

test t("global variable");

void f()
{
        static test t("static variable");

        test t2("Local variable");

        cout << "Function executed" << endl;
}


int main()
{
        test t("local to main");

        cout << "Program start" << endl;

        f();

        cout << "Program end" << endl;
        return 0;
}

परिणाम वे नहीं थे जिनकी मुझे उम्मीद थी। स्थिर वस्तु के लिए निर्माता को तब तक नहीं बुलाया गया जब तक कि पहली बार फ़ंक्शन को नहीं बुलाया गया। यहाँ उत्पादन है:

global variable created
local to main created
Program start
static variable created
Local variable created
Function executed
Local variable destroyed
Program end
local to main destroyed
static variable destroyed
global variable destroyed

30
स्पष्टीकरण के रूप में: स्थैतिक चर को प्रारंभिक किया जाता है, पहली बार निष्पादन इसकी घोषणा को हिट करता है, न कि जब युक्त फ़ंक्शन को बुलाया जाता है। यदि आपके पास फ़ंक्शन की शुरुआत में एक स्थिर है (जैसे आपके उदाहरण में) ये समान हैं, लेकिन जरूरी नहीं हैं: उदाहरण के लिए यदि आपके पास 'अगर (...) {स्थैतिक MyClass x है; ...} ', तो' x 'को उस स्थिति में उस फ़ंक्शन के पहले निष्पादन के दौरान ALL पर आरंभीकृत नहीं किया जाएगा जहां यदि कथन की स्थिति गलत का मूल्यांकन करती है।
इवान

4
लेकिन क्या यह रनटाइम ओवरहेड की ओर नहीं ले जाता है, क्योंकि प्रत्येक बार जब स्थिर वैरिएबल का उपयोग किया जाता है, तो प्रोग्राम को यह जांचना होता है कि क्या यह पहले उपयोग किया गया है, यदि नहीं, तो इसे आरंभीकृत करना होगा? उस मामले में उस तरह का एक सा बेकार है।
HelloGoodbye

सही चित्रण
Des1gnWizard

@veio: हाँ, आरंभीकरण धागा-सुरक्षित है। अधिक विवरण के लिए उस प्रश्न को देखें: stackoverflow.com/questions/23829389/…
Rémi

2
@ हेलो गुडबी: हाँ, यह एक रनवे ओवरहेड की ओर जाता है। उस प्रश्न को भी देखें: stackoverflow.com/questions/23829389/…
रेमी

53

C ++ मानक से कुछ प्रासंगिक क्रिया:

3.6.2 गैर-स्थानीय वस्तुओं का प्रारंभिककरण [basic.start.init]

1

स्थैतिक भंडारण अवधि ( basic.stc.static ) वाली वस्तुओं के लिए भंडारण किसी अन्य इनिशियलाइज़ेशन के होने से पहले शून्य-प्रारंभिक ( dcl.init ) होगा। स्थिर भावों के साथ POD प्रकारों ( basic.types ) की वस्तुओं को स्थिर भाव ( expr.const ) के साथ आरंभीकृत किया जा सकता है, इससे पहले कि किसी भी गतिशील आरंभीकरण की शुरुआत हो। एक ही अनुवाद इकाई में स्थिर भंडारण अवधि और गतिशील रूप से आरंभिक नाम के साथ नेमस्पेस स्कोप की वस्तुओं को उस क्रम में आरंभीकृत किया जाएगा जिसमें उनकी परिभाषा अनुवाद इकाई में दिखाई देती है। [नोट: dcl.init.aggr उस क्रम का वर्णन करता है जिसमें कुल सदस्य आरंभिक होते हैं। स्थानीय स्थैतिक वस्तुओं के आरंभ का वर्णन stmt.dcl में किया गया है । ]

[संकलक लेखकों के लिए अधिक स्वतंत्रता जोड़ने के नीचे और अधिक पाठ]

6.7 घोषणा विवरण [stmt.dcl]

...

4

स्थिर भंडारण अवधि ( basic.stc.static ) के साथ सभी स्थानीय वस्तुओं का शून्य-आरंभीकरण ( dcl.init ) किसी भी अन्य आरंभ होने से पहले किया जाता है। स्थैतिक भंडारण अवधि के साथ POD प्रकार ( basic.types ) की एक स्थानीय वस्तु स्थिर-अभिव्यक्ति के साथ आरम्भ की जाती है, जब इसका ब्लॉक पहले दर्ज किया जाता है। एक कार्यान्वयन को अन्य स्थानीय वस्तुओं के स्थैतिक भंडारण की अवधि के साथ आरंभिक प्रदर्शन करने की अनुमति है उन्हीं शर्तों के तहत जिन्हें क्रियान्वयन की अनुमति दी गई है कि वे स्थैतिक रूप से नामस्थान क्षेत्र में स्थैतिक भंडारण अवधि के साथ किसी ऑब्जेक्ट को इनिशियलाइज़ करें ( basic.start.init))। अन्यथा ऐसी वस्तु को इनिशियलाइज़ किया जाता है जब पहली बार नियंत्रण उसकी घोषणा से गुजरता है; इस तरह की वस्तु को इसके आरंभ के पूरा होने पर आरंभिक माना जाता है। यदि अपवाद को फेंकने से आरंभीकरण बाहर निकलता है, तो इनिशियलाइज़ेशन पूरा नहीं होता है, इसलिए यह फिर से कोशिश की जाएगी कि अगली बार नियंत्रण घोषणा में प्रवेश कर जाए। यदि नियंत्रण घोषणा (पुनरावर्ती) में फिर से प्रवेश करता है जबकि ऑब्जेक्ट को आरंभीकृत किया जा रहा है, तो व्यवहार अपरिभाषित है। [ उदाहरण:

      int foo(int i)
      {
          static int s = foo(2*i);  // recursive call - undefined
          return i+1;
      }

- अंतिम उदाहरण ]

5

स्थैतिक भंडारण अवधि के साथ एक स्थानीय वस्तु के लिए विध्वंसक को क्रियान्वित किया जाएगा यदि और केवल अगर चर का निर्माण किया गया था। [नोट: basic.start.term उस क्रम का वर्णन करता है जिसमें स्थैतिक भंडारण अवधि वाली स्थानीय वस्तुएँ नष्ट हो जाती हैं। ]


इसने मेरे सवाल का जवाब दिया और स्वीकार किए गए उत्तर के विपरीत "उपाख्यानात्मक प्रमाण" पर भरोसा नहीं करता है। मैं विशेष रूप से स्टेटिक-इनिशियलाइज़्ड फंक्शन लोकल स्टैटिक ऑब्जेक्ट्स के कंस्ट्रक्टर में अपवादों के इस उल्लेख को देख रहा था:If the initialization exits by throwing an exception, the initialization is not complete, so it will be tried again the next time control enters the declaration.
Bensge

26

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


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

लगता है कि लिंक अब टूट गया है, 7 साल बाद।
स्टीव

1
हां, लिंक टूट गया। : यहाँ एक संग्रह है web.archive.org/web/20100328062506/http://www.acm.org/...
यूजीन

10

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

@ एडडम: कंपाइलर द्वारा कोड के पर्दे के इंजेक्शन के पीछे, आपके द्वारा देखे गए परिणाम का कारण है।


5

मैं एडम पियर्स से फिर से कोड का परीक्षण करने की कोशिश करता हूं और दो और मामले जोड़े हैं: कक्षा में स्थिर चर और पीओडी प्रकार। मेरा कंपाइलर g ++ 4.8.1 है, विंडोज ओएस (MinGW-32) में। परिणाम स्थिर चर है जो वैश्विक चर के साथ समान व्यवहार करता है। मुख्य समारोह में प्रवेश करने से पहले इसके निर्माता को बुलाया जाएगा।

  • निष्कर्ष (जी ++ के लिए, विंडोज वातावरण):

    1. वैश्विक चर और कक्षा में स्थिर सदस्य : मुख्य फ़ंक्शन (1) दर्ज करने से पहले कंस्ट्रक्टर को बुलाया जाता है
    2. स्थानीय स्थिर चर : कंस्ट्रक्टर को केवल तब बुलाया जाता है जब निष्पादन पहली बार अपनी घोषणा पर पहुंचता है।
    3. यदि स्थानीय स्थिर चर POD प्रकार है , तो इसे मुख्य फ़ंक्शन (1) दर्ज करने से पहले आरंभीकृत किया जाता है । पीओडी प्रकार के लिए उदाहरण: स्थिर इंट संख्या = 10;

(1) : सही स्थिति यह होनी चाहिए: "एक ही अनुवाद इकाई से किसी भी फ़ंक्शन को पहले कहा जाता है"। हालांकि, सरल के लिए, जैसा कि नीचे दिए गए उदाहरण में है, तो यह मुख्य कार्य है।

<iostream> शामिल करें

#include < string>

using namespace std;

class test
{
public:
   test(const char *name)
            : _name(name)
    {
            cout << _name << " created" << endl;
    }

    ~test()
    {
            cout << _name << " destroyed" << endl;
    }

    string _name;
    static test t; // static member
 };
test test::t("static in class");

test t("global variable");

void f()
{
    static  test t("static variable");
    static int num = 10 ; // POD type, init before enter main function

    test t2("Local variable");
    cout << "Function executed" << endl;
}

int main()
{
    test t("local to main");
    cout << "Program start" << endl;
    f();
    cout << "Program end" << endl;
    return 0;
 }

परिणाम:

static in class created
global variable created
local to main created
Program start
static variable created
Local variable created
Function executed
Local variable destroyed
Program end
local to main destroyed
static variable destroyed
global variable destroyed
static in class destroyed

लिनक्स env में कोई भी परीक्षण किया गया?


3

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

फंक्शन स्कोप के भीतर स्टेटिक वैरिएबल्स को एक समान माना जाता है, स्कूपिंग पूरी तरह से एक भाषा स्तर का निर्माण है।

इस कारण से आपको गारंटी दी जाती है कि अपरिभाषित मान के बजाय एक स्थिर चर 0 (जब तक आप कुछ और निर्दिष्ट नहीं करते) को प्रारंभ किया जाएगा।

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

C ++ में (वैश्विक रूप से स्कोप्ड) स्थिर ऑब्जेक्ट्स में उनके कंस्ट्रक्टर होते हैं जिन्हें प्रोग्राम का हिस्सा कहा जाता है, C रनटाइम लाइब्रेरी के नियंत्रण में। विजुअल C ++ के तहत वस्तुओं को इनिशियलाइज़ किए जाने वाले कम से कम आदेश को init_seg pragma द्वारा नियंत्रित किया जा सकता है ।


4
यह सवाल फंक्शन-स्कॉप्ड स्टैटिक्स के बारे में है। कम से कम जब उनके पास nontrivial कंस्ट्रक्टर होते हैं, तो वे फ़ंक्शन में पहले प्रवेश पर आरंभीकृत होते हैं। या अधिक विशेष रूप से, जब वह रेखा पहुँच जाती है।
एडम मिट्ज

सच - लेकिन सवाल चर को आवंटित स्थान के बारे में बात करता है, और सरल डेटा प्रकारों का उपयोग करता है। कोड सेगमेंट में अभी भी अंतरिक्ष आवंटित किया गया है
रोब वॉकर

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

5
कोड सेगमेंट के अंदर चर कभी आवंटित नहीं होते हैं; इस तरह वे लिखने में सक्षम नहीं होंगे।
बोटिस्मेरिक

1
स्टैटिक वैरिएबल को डेटा सेगमेंट या bss सेगमेंट में स्पेस आवंटित किया जाता है जो इस बात पर निर्भर करता है कि वे इनिशियलाइज़ हैं या नहीं।
EmptyData

3

या यह प्रारंभिक है जब doSomething () को पहली बार कहा जाता है?

हाँ यही है। यह, अन्य बातों के अलावा, जब आप उचित रूप से कोशिश / पकड़ ब्लॉक के अंदर उदाहरण के लिए, विश्व स्तर पर एक्सेस डेटा संरचनाओं को इनिशियलाइज़ करते हैं। के बजाय एग

int foo = init(); // bad if init() throws something

int main() {
  try {
    ...
  }
  catch(...){
    ...
  }
}

तुम लिख सकते हो

int& foo() {
  static int myfoo = init();
  return myfoo;
}

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

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