लूप के अंदर वैरिएबल घोषित करना, अच्छा अभ्यास या बुरा अभ्यास?


265

प्रश्न # 1: क्या पाश के अंदर एक चर को एक अच्छा अभ्यास या बुरा अभ्यास घोषित किया जाता है?

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

उदाहरण:

for(int counter = 0; counter <= 10; counter++)
{
   string someString = "testing";

   cout << someString;
}

प्रश्न # 2: क्या अधिकांश कंपाइलर्स को पता चलता है कि वेरिएबल पहले ही घोषित हो चुका है और बस उस हिस्से को छोड़ देना है, या क्या यह वास्तव में हर बार मेमोरी में इसके लिए जगह बनाता है?


29
उनके उपयोग के करीब रखें, जब तक कि प्रोफाइलिंग अन्यथा न कहे।
मूविंग डक

1
यहाँ कुछ ऐसे ही प्रश्न हैं: stackoverflow.com/questions/982963/… stackoverflow.com/questions/407255/…
drnewman

3
@drnewman मैंने उन थ्रेड्स को पढ़ा, लेकिन उन्होंने मेरे सवाल का जवाब नहीं दिया। मैं समझता हूं कि छोरों के अंदर चर घोषित करना काम करता है। मैं सोच रहा था कि क्या यह एक अच्छा अभ्यास है या अगर यह कुछ बचा जाना है।
जेरमिर्र

जवाबों:


348

यह उत्कृष्ट अभ्यास है।

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

इस तरफ:

  • यदि चर का नाम थोड़ा "सामान्य" है (जैसे "i"), तो बाद में आपके कोड में उसी नाम के किसी अन्य चर के साथ इसे मिलाने का कोई जोखिम नहीं है ( -Wshadowजीसीसी पर चेतावनी निर्देश का उपयोग करके इसे कम किया जा सकता है )

  • कंपाइलर जानता है कि चर गुंजाइश लूप के अंदर तक ही सीमित है, और इसलिए चर को गलती से संदर्भित किसी अन्य स्थान पर होने पर एक उचित त्रुटि संदेश जारी करेगा।

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

संक्षेप में, आप इसे करने के लिए सही हैं।

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

{
    int i, retainValue;
    for (i=0; i<N; i++)
    {
       int tmpValue;
       /* tmpValue is uninitialized */
       /* retainValue still has its previous value from previous loop */

       /* Do some stuff here */
    }
    /* Here, retainValue is still valid; tmpValue no longer */
}

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

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

यह एक if(){...}ब्लॉक के बाहर भी सच है । आमतौर पर, इसके बजाय:

    int result;
    (...)
    result = f1();
    if (result) then { (...) }
    (...)
    result = f2();
    if (result) then { (...) }

यह लिखना अधिक सुरक्षित है:

    (...)
    {
        int const result = f1();
        if (result) then { (...) }
    }
    (...)
    {
        int const result = f2();
        if (result) then { (...) }
    }

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

यहां तक ​​कि संकलक बेहतर मदद करेगा: यह मानते हुए कि भविष्य में, कोड के कुछ गलत परिवर्तन के बाद, resultठीक से प्रारंभ नहीं किया गया है f2()। दूसरा संस्करण बस काम करने से इनकार कर देगा, संकलन समय पर एक स्पष्ट त्रुटि संदेश (जैसे कि रन समय से बेहतर)। पहले संस्करण में कुछ भी नहीं होगा, के परिणाम को f1()दूसरी बार परीक्षण किया जाएगा , जिसके परिणाम के लिए भ्रमित किया जा रहा है f2()

पूरक जानकारी

ओपन-सोर्स टूल कैपचेक (सी / सी ++ कोड के लिए एक स्थिर विश्लेषण उपकरण) चर के इष्टतम दायरे के बारे में कुछ उत्कृष्ट संकेत प्रदान करता है।

आवंटन पर टिप्पणी के जवाब में: उपरोक्त नियम C में सत्य है, लेकिन कुछ C ++ वर्गों के लिए नहीं हो सकता है।

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

हालाँकि, C ++ क्लासेस के लिए, यह कंस्ट्रक्टर चीज़ है, जिसके बारे में मैं बहुत कम जानता हूँ। मुझे लगता है कि आवंटन संभवत: मुद्दा नहीं होगा, क्योंकि कंपाइलर एक ही स्थान का पुन: उपयोग करने के लिए पर्याप्त चतुर होगा, लेकिन आरंभीकरण प्रत्येक लूप पुनरावृत्ति पर होने की संभावना है।


4
बहुत बढ़िया जवाब। यह वही है जो मैं देख रहा था, और यहां तक ​​कि मुझे कुछ ऐसी जानकारी दी जो मुझे महसूस नहीं हुई। मुझे महसूस नहीं हुआ कि गुंजाइश केवल लूप के अंदर ही रहती है। जवाब देने के लिए धन्यवाद!
जेरमिर्र

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

4
@JeramyRR: बिल्कुल नहीं - संकलक को यह जानने का कोई तरीका नहीं है कि क्या वस्तु का उसके निर्माता या विध्वंसक में सार्थक दुष्प्रभाव हैं।
ildjarn

2
@Iron: दूसरी ओर, जब आप पहले आइटम की घोषणा करते हैं, तो आपको असाइनमेंट ऑपरेटर को कई कॉल मिलते हैं; जो आमतौर पर एक वस्तु के निर्माण और नष्ट करने के समान है।
बिली ओनेल

4
@ बिलियन: के लिए stringऔर vectorविशेष रूप से, असाइनमेंट ऑपरेटर आवंटित बफर को प्रत्येक लूप को पुन: उपयोग कर सकता है, जो (आपके लूप के आधार पर) एक बड़ी बचत हो सकती है।
मूविंग डक

22

आमतौर पर, इसे बहुत पास रखने के लिए एक बहुत अच्छा अभ्यास है।

कुछ मामलों में, एक विचार होगा जैसे प्रदर्शन जो चर को लूप से बाहर खींचने को सही ठहराता है।

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

मान लीजिए कि आप उन अनावश्यक रचनाओं / आवंटन से बचना चाहते हैं, तो आप इसे इस प्रकार लिखेंगे:

for (int counter = 0; counter <= 10; counter++) {
   // compiler can pull this out
   const char testing[] = "testing";
   cout << testing;
}

या आप स्थिरांक को बाहर खींच सकते हैं:

const std::string testing = "testing";
for (int counter = 0; counter <= 10; counter++) {
   cout << testing;
}

क्या अधिकांश कंपाइलर्स को एहसास होता है कि वेरिएबल पहले ही घोषित हो चुका है और बस उस हिस्से को छोड़ दें, या क्या यह वास्तव में हर बार मेमोरी के लिए स्पॉट बनाता है?

यह उस स्थान का पुन: उपयोग कर सकता है जो चर का उपभोग करता है, और यह आपके पाश से आक्रमणकारियों को खींच सकता है। कास्ट चार सरणी (ऊपर) के मामले में - उस सरणी को बाहर निकाला जा सकता है। हालाँकि, कंस्ट्रक्टर और डिस्ट्रक्टर को ऑब्जेक्ट के मामले में प्रत्येक पुनरावृत्ति पर निष्पादित किया जाना चाहिए (जैसे कि std::string)। के मामले में std::string, उस 'स्पेस' में एक पॉइंटर शामिल होता है जिसमें वर्णों का प्रतिनिधित्व करने वाला गतिशील आवंटन होता है। तो यह:

for (int counter = 0; counter <= 10; counter++) {
   string testing = "testing";
   cout << testing;
}

प्रत्येक मामले में निरर्थक नकल की आवश्यकता होगी, और यदि एसएसओ वर्ण गणना के लिए दहलीज के ऊपर चर बैठता है (और एसएसओ आपकी एसटीडी लाइब्रेरी द्वारा कार्यान्वित किया जाता है)।

यह कर रहा हूं:

string testing;
for (int counter = 0; counter <= 10; counter++) {
   testing = "testing";
   cout << testing;
}

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

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


1
फ्लोट या इंट जैसे बेसिक डेटाटाइप्स के बारे में, लूप के अंदर वैरिएबल को घोषित करने से लूप के बाहर वैरिएबल घोषित करने की तुलना में धीमी हो जाएगी, क्योंकि इसे वैरिएबल के लिए एक स्थान आवंटित करना होगा?
कास्परोवॉव

2
@ Kasparov92 संक्षिप्त उत्तर है "नहीं। उस अनुकूलन को अनदेखा करें और बेहतर पठनीयता / स्थानीयता के लिए संभव होने पर इसे लूप में रखें। कंपाइलर आपके लिए उस माइक्रो-ऑप्टिमाइज़ेशन का प्रदर्शन कर सकता है।" अधिक विस्तार से, जो कि अंतत: संकलक के लिए तय करने के लिए है कि मंच, अनुकूलन स्तर आदि के लिए सबसे अच्छा क्या है, के आधार पर एक लूप के अंदर एक साधारण इंट / फ्लोट को आमतौर पर स्टैक पर रखा जाएगा। एक कंपाइलर निश्चित रूप से लूप के बाहर जा सकता है और ऐसा करने में ऑप्टिमाइज़ेशन होने पर स्टोरेज को फिर से उपयोग कर सकता है। व्यावहारिक प्रयोजनों के लिए, यह एक बहुत ही बहुत बहुत छोटे अनुकूलन ... होगा
जस्टिन

1
@ Kasparov92… (cont) जिसे आप केवल उन वातावरण / अनुप्रयोगों पर विचार करेंगे, जहाँ हर चक्र को गिना जाता है। उस स्थिति में, आप केवल असेंबली का उपयोग करने पर विचार कर सकते हैं।
जस्टिन

14

C ++ के लिए यह निर्भर करता है कि आप क्या कर रहे हैं। ठीक है, यह बेवकूफ कोड है लेकिन कल्पना कीजिए

class myTimeEatingClass
{
 public:
 //constructor
      myTimeEatingClass()
      {
          sleep(2000);
          ms_usedTime+=2;
      }
      ~myTimeEatingClass()
      {
          sleep(3000);
          ms_usedTime+=3;
      }
      const unsigned int getTime() const
      {
          return  ms_usedTime;
      }
      static unsigned int ms_usedTime;
};
myTimeEatingClass::ms_CreationTime=0; 
myFunc()
{
    for (int counter = 0; counter <= 10; counter++) {

        myTimeEatingClass timeEater();
        //do something
    }
    cout << "Creating class took "<< timeEater.getTime() <<"seconds at all<<endl;

}
myOtherFunc()
{
    myTimeEatingClass timeEater();
    for (int counter = 0; counter <= 10; counter++) {
        //do something
    }
    cout << "Creating class took "<< timeEater.getTime() <<"seconds at all<<endl;

}

जब तक आपको myFunc का आउटपुट नहीं मिलेगा तब तक आप 55 सेकंड प्रतीक्षा करेंगे। सिर्फ इसलिए कि प्रत्येक लूप कंस्ट्रक्टर और डिस्ट्रक्टर को एक साथ खत्म करने के लिए 5 सेकंड की आवश्यकता होती है।

जब तक आपको myOtherFunc का आउटपुट नहीं मिलेगा तब तक आपको 5 सेकंड की आवश्यकता होगी।

बेशक, यह एक पागल उदाहरण है।

लेकिन यह दिखाता है कि यह एक प्रदर्शन मुद्दा बन सकता है जब प्रत्येक लूप उसी निर्माण किया जाता है जब निर्माणकर्ता और / या विध्वंसक को हर समय की आवश्यकता होती है।


2
ठीक है, तकनीकी रूप से दूसरे संस्करण में आपको केवल 2 सेकंड में आउटपुट मिल जाएगा, क्योंकि आपने अभी तक ऑब्जेक्ट को नष्ट नहीं किया है .....
क्रिस

12

मैंने जेरेमीआरआर के सवालों का जवाब देने के लिए पोस्ट नहीं किया (जैसा कि वे पहले ही जवाब दे चुके हैं); इसके बजाय, मैंने केवल सुझाव देने के लिए पोस्ट किया।

जेरेमीआरआर के लिए, आप ऐसा कर सकते हैं:

{
  string someString = "testing";   

  for(int counter = 0; counter <= 10; counter++)
  {
    cout << someString;
  }

  // The variable is in scope.
}

// The variable is no longer in scope.

मुझे नहीं पता कि क्या आपको पता है (मैंने पहली बार प्रोग्रामिंग शुरू नहीं की थी), उस कोष्ठक (जब तक वे जोड़े में हैं) को कोड के भीतर कहीं भी रखा जा सकता है, न कि केवल "अगर", "के लिए", " जबकि ", आदि।

मेरा कोड Microsoft Visual C ++ 2010 एक्सप्रेस में संकलित है, इसलिए मुझे पता है कि यह काम करता है; इसके अलावा, मैंने ब्रैकेट्स के बाहर चर का उपयोग करने की कोशिश की है जिसे यह परिभाषित किया गया था और मुझे एक त्रुटि मिली, इसलिए मुझे पता है कि चर "नष्ट" हो गया था।

मुझे नहीं पता कि इस पद्धति का उपयोग करना बुरा है, क्योंकि बहुत सारे अनलॉकेटेड ब्रैकेट जल्दी से कोड को अपठनीय बना सकते हैं, लेकिन हो सकता है कि कुछ टिप्पणियां चीजों को साफ कर सकें।


4
मेरे लिए, यह एक बहुत ही वैध उत्तर है जो प्रश्न से सीधे एक सुझाव लाता है। आपको मेरा वोट है!
एलेक्सिस लेक्लर

0

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

stack<int> st;
st.push(s);
cout<<s<<" ";
vis[s]=1;
int flag=0;
int top=0;
while(!st.empty()){
    top = st.top();
    for(int i=0;i<g[top].size();i++){
        if(vis[g[top][i]] != 1){
            st.push(g[top][i]);
            cout<<g[top][i]<<" ";
            vis[g[top][i]]=1;
            flag=1;
            break;
        }
    }
    if(!flag){
        st.pop();
    }
}

अब लूप के अंदर पूर्णांक डालें, इससे आपको सही उत्तर मिलेगा ...

stack<int> st;
st.push(s);
cout<<s<<" ";
vis[s]=1;
// int flag=0;
// int top=0;
while(!st.empty()){
    int top = st.top();
    int flag = 0;
    for(int i=0;i<g[top].size();i++){
        if(vis[g[top][i]] != 1){
            st.push(g[top][i]);
            cout<<g[top][i]<<" ";
            vis[g[top][i]]=1;
            flag=1;
            break;
        }
    }
    if(!flag){
        st.pop();
    }
}

यह पूरी तरह से दर्शाता है कि साहब @justin दूसरी टिप्पणी में क्या कह रहे थे .... यह यहां देखें । बस इसे एक शॉट दें .... आपको यह मिल जाएगा। यह मदद करें।


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

आपके द्वारा पोस्ट किए गए कोड में, समस्या परिभाषा नहीं है, लेकिन प्रारंभिक भाग है। flag0 प्रत्येक whileपुनरावृत्ति पर पुनर्निवेश होना चाहिए । यह एक तर्क समस्या है, परिभाषा समस्या नहीं है।
मार्टिन वेरोन्यू

0

अध्याय 4.8 ब्लॉक संरचना कश्मीर एंड आर में है सी भाषा 2.Ed. प्रोग्रामिंग :

हर बार ब्लॉक में प्रवेश करने पर एक स्वचालित चर घोषित किया जाता है और एक ब्लॉक में इनिशियलाइज़ किया जाता है।

मुझे पुस्तक में प्रासंगिक विवरण देखने में चूक हुई होगी जैसे:

एक ब्लॉक में घोषित और आरंभिक स्वचालित चर को ब्लॉक में प्रवेश करने से पहले केवल एक बार आवंटित किया जाता है।

लेकिन एक साधारण परीक्षण धारण की गई धारणा को प्रमाणित कर सकता है:

 #include <stdio.h>                                                                                                    

 int main(int argc, char *argv[]) {                                                                                    
     for (int i = 0; i < 2; i++) {                                                                                     
         for (int j = 0; j < 2; j++) {                                                                                 
             int k;                                                                                                    
             printf("%p\n", &k);                                                                                       
         }                                                                                                             
     }                                                                                                                 
     return 0;                                                                                                         
 }                                                                                                                     
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.