क्या लूप के भीतर एक चर घोषित करने के लिए कोई उपरि है? (C ++)


158

मैं बस सोच रहा हूं कि अगर आपने ऐसा कुछ किया तो गति या दक्षता का कोई नुकसान होगा:

int i = 0;
while(i < 100)
{
    int var = 4;
    i++;
}

जो int varएक सौ बार घोषित करता है । यह मुझे लगता है जैसे वहाँ होगा, लेकिन मुझे यकीन नहीं है। इसके बजाय ऐसा करना अधिक व्यावहारिक / तेज़ होगा:

int i = 0;
int var;
while(i < 100)
{
    var = 4;
    i++;
}

या वे एक ही हैं, गति और दक्षता बुद्धिमान?


7
स्पष्ट होने के लिए, उपरोक्त कोड एक सौ बार "घोषणा" नहीं करता है।
jason

1
@ रबारकी: संदर्भित प्रश्न एक सटीक डुप्लिकेट नहीं है क्योंकि यह एक भाषा को निर्दिष्ट नहीं करता है। यह प्रश्न C ++ के लिए विशिष्ट है । लेकिन आपके संदर्भित प्रश्न के उत्तर के अनुसार, उत्तर भाषा और संभवतः संकलक पर निर्भर करता है।
डेविड आरआर

2
@ यासन यदि कोड का पहला स्निपेट एक सौ बार चर 'var' की घोषणा नहीं करता है, तो क्या आप बता सकते हैं कि क्या हो रहा है? क्या यह केवल चर को एक बार घोषित करता है और इसे 100 बार आरंभ करता है? मुझे लगता है कि कोड 100 बार चर घोषित करता है और शुरू करता है, क्योंकि लूप में सब कुछ 100 बार निष्पादित होता है। धन्यवाद।
randomUser47534

जवाबों:


194

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


50
मैं उन लोगों को शुभकामना देता हूं जो कम से कम कॉलेज से पढ़ते हैं और इस मूल बात को जानते हैं। एक बार जब उन्होंने मुझे एक लूप के अंदर एक चर घोषित करते हुए हँसाया और मैं सोच रहा था कि क्या गलत है जब तक कि उन्होंने ऐसा नहीं करने का कारण बताया और मैं "डब्ल्यूटीएफ!" जैसा था।
एमएमएक्स

18
क्या आप वाकई स्टैक स्पेस के बारे में बात कर रहे हैं। इस तरह एक चर एक रजिस्टर में भी हो सकता है।
टोटो

3
@ टोटो इस तरह का एक वैरिएबल भी कहीं नहीं हो सकता है - varवैरिएबल को इनिशियलाइज़ किया गया है लेकिन कभी इस्तेमाल नहीं किया गया है, इसलिए एक उचित ऑप्टिमाइज़र इसे पूरी तरह से हटा सकता है (दूसरे स्निपेट को छोड़कर अगर लूप के बाद वैरिएबल को कहीं इस्तेमाल किया गया था)।
सियापन

@ मेहरदाद अफशरी एक पाश में एक चर इसके निर्माणकर्ता को प्रति बार एक बार बुलाया जाता है। EDIT - मैं आपको नीचे इसका उल्लेख करता हूं, लेकिन मुझे लगता है कि यह स्वीकृत उत्तर के रूप में भी उल्लेख के योग्य है।
२०:०२ पर हुडैटिकस

106

आदिम प्रकार और POD प्रकारों के लिए, इससे कोई फर्क नहीं पड़ता। कंपाइलर फ़ंक्शन की शुरुआत में वेरिएबल के लिए स्टैक स्पेस आवंटित करेगा और फ़ंक्शन को दोनों मामलों में वापस आने पर इसे हटा देगा।

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


42
सही विचार गलत कारण। लूप के बाहर चर। एक बार निर्माण हुआ, एक बार नष्ट हो गया लेकिन असाइनमेंट ऑपरेटर ने हर पुनरावृत्ति को लागू किया। लूप के अंदर चर। Constructe / Desatructor ने हर पुनरावृत्ति को लेकिन शून्य असाइनमेंट ऑपरेशनों को लागू किया।
मार्टिन यॉर्क

8
यह सबसे अच्छा जवाब है लेकिन ये टिप्पणियां भ्रमित करने वाली हैं। एक रचनाकार और एक असाइनमेंट ऑपरेटर को कॉल करने के बीच एक बड़ा अंतर है।
एंड्रयू ग्रांट

1
यह है सच है, तो पाश शरीर ही नहीं, आरंभीकरण के लिए, वैसे भी काम करता है। और अगर सिर्फ एक बॉडी-इंडिपेंडेंट / निरंतर इनिशियलाइज़ेशन है, तो ऑप्टिमाइज़र इसे फहरा सकता है।
पीटरचेन

7
@ और ग्रांट: क्यों। असाइनमेंट ऑपरेटर को आमतौर पर tmp में कॉपी निर्माण के रूप में परिभाषित किया जाता है, जिसके बाद swap (अपवाद सुरक्षित होना) के बाद tmp को नष्ट करना होता है। इस प्रकार असाइनमेंट ऑपरेटर ऊपर चक्र निर्माण / नष्ट करने के लिए अलग नहीं है। विशिष्ट असाइनमेंट ऑपरेटर के उदाहरण के लिए stackoverflow.com/questions/255612/… देखें ।
मार्टिन यॉर्क

1
यदि निर्माण / विनाश महंगा है, तो उनकी कुल लागत ऑपरेटर की लागत पर एक उचित ऊपरी सीमा है =। लेकिन असाइनमेंट वास्तव में सस्ता हो सकता है। इसके अलावा, जब हम इस चर्चा को ints से C ++ प्रकार तक विस्तारित करते हैं, तो कोई 'var = 4' को सामान्य कर सकता है क्योंकि 'उसी प्रकार के मान से वेरिएबल असाइन करें' की तुलना में कुछ अन्य ऑपरेशन।
ग्रैग्गो

69

वे दोनों समान हैं, और यहां बताया गया है कि कंपाइलर क्या करता है, यह देखकर आप यह पता लगा सकते हैं (भले ही अनुकूलन उच्च पर सेट न हो):

देखें कि संकलक (gcc 4.0) आपके सरल उदाहरणों के लिए क्या करता है:

1.c:

main(){ int var; while(int i < 100) { var = 4; } }

gcc -S 1. सी

1.s:

_main:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $24, %esp
    movl    $0, -16(%ebp)
    jmp L2
L3:
    movl    $4, -12(%ebp)
L2:
    cmpl    $99, -16(%ebp)
    jle L3
    leave
    ret

2.c

main() { while(int i < 100) { int var = 4; } }

gcc -S 2.c

2.s:

_main:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $24, %esp
        movl    $0, -16(%ebp)
        jmp     L2
L3:
        movl    $4, -12(%ebp)
L2:
        cmpl    $99, -16(%ebp)
        jle     L3
        leave
        ret

इनसे, आप दो चीजें देख सकते हैं: सबसे पहले, कोड दोनों में समान है।

दूसरे, var के लिए स्टोरेज को लूप के बाहर आवंटित किया गया है:

         subl    $24, %esp

और अंत में लूप की एकमात्र चीज़ असाइनमेंट और कंडीशन चेक है:

L3:
        movl    $4, -12(%ebp)
L2:
        cmpl    $99, -16(%ebp)
        jle     L3

जो लगभग उतना ही कुशल है जितना कि आप लूप को पूरी तरह से हटाए बिना हो सकते हैं।


2
"जो कि लूप को पूरी तरह से हटाए बिना आपके लिए जितना कुशल हो सकता है" काफी नहीं है। आंशिक रूप से लूप को अनियंत्रित करना (इसे प्रति बार 4 बार कहना) नाटकीय रूप से इसे गति देगा। अनुकूलन करने के लिए शायद कई अन्य तरीके हैं ... हालांकि अधिकांश आधुनिक संकलकों को शायद यह एहसास होगा कि वहाँ कोई मतलब नहीं है। अगर बाद में 'i' का इस्तेमाल किया गया, तो यह बस 'i' = 100 को सेट करेगा।
darron

यह मानकर कि कोड 'i' से बढ़ा हुआ है ... बिल्कुल बदल गया है।
darron

जैसा की ओरिजिनल पोस्ट था!
एलेक्स ब्राउन

2
मुझे ऐसे उत्तर पसंद हैं जो प्रमाण के साथ सिद्धांत को वापस करते हैं! ASM को समान कोड होने के सिद्धांत का समर्थन करते हुए देखकर अच्छा लगा। +1
ज़ेवि मोंटेरो

1
मैंने वास्तव में प्रत्येक संस्करण के लिए मशीन कोड बनाकर परिणाम तैयार किए। इसे चलाने की जरूरत नहीं है।
एलेक्स ब्राउन

14

इन दिनों इसे लूप के अंदर घोषित करना बेहतर होता है जब तक कि यह स्थिर न हो क्योंकि कंपाइलर कोड को बेहतर तरीके से ऑप्टिमाइज़ कर सकेगा (वेरिएबल स्कोप को कम करना)।

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


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

3
यदि आप एक ही अस्थायी चर नाम का उपयोग करते हुए दो अलग-अलग छोरों का पता लगा लेंगे, तो यह पता नहीं चलेगा।
जोशुआ

11

अधिकांश आधुनिक संकलक आपके लिए इसका अनुकूलन करेंगे। कहा जा रहा है कि मैं आपके पहले उदाहरण का उपयोग करूंगा क्योंकि मुझे यह अधिक पठनीय लगता है।


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

आप सही हैं, "अनुकूलन" गलत शब्द है लेकिन मैं एक बेहतर के लिए एक नुकसान में हूं।
एंड्रयू हरे

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

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

9

अंतर्निहित प्रकार के लिए संभवतः 2 शैलियों के बीच कोई अंतर नहीं होगा (शायद उत्पन्न कोड के ठीक नीचे)।

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

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

मान लें कि आपके पास एक ऐसा LockMgrवर्ग है जो निर्माण होने पर एक महत्वपूर्ण खंड प्राप्त करता है और नष्ट होने पर इसे जारी करता है:

while (i< 100) {
    LockMgr lock( myCriticalSection); // acquires a critical section at start of
                                      //    each loop iteration

    // do stuff...

}   // critical section is released at end of each loop iteration

इससे काफी अलग है:

LockMgr lock( myCriticalSection);
while (i< 100) {

    // do stuff...

}

6

दोनों छोरों में एक ही दक्षता है। वे दोनों एक अनंत समय लेंगे :) यह छोरों के अंदर वृद्धि करने के लिए एक अच्छा विचार हो सकता है।


आह हां, मैं अंतरिक्ष दक्षता को संबोधित करना भूल गया - यह ठीक है - दोनों के लिए 2 इंच। यह सिर्फ मुझे अजीब लगता है कि प्रोग्रामर पेड़ के लिए जंगल को याद कर रहे हैं - ये सभी कुछ कोड के बारे में सुझाव जो समाप्त नहीं होते हैं।
लैरी वतनबे

यह ठीक है अगर वे समाप्त नहीं करते हैं। न ही उनमें से किसी को बुलाया जाता है। :-)
नोसरेडना

2

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


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

कृपया अपना सेटअप और परिणाम पोस्ट करें।
jxramos

2
#include <stdio.h>
int main()
{
    for(int i = 0; i < 10; i++)
    {
        int test;
        if(i == 0)
            test = 100;
        printf("%d\n", test);
    }
}

ऊपर कोड हमेशा 100 10 बार प्रिंट करता है जिसका अर्थ है कि लूप के अंदर स्थानीय चर प्रत्येक फ़ंक्शन कॉल के अनुसार एक बार आवंटित किया जाता है।


0

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

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


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

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

-1

केवल दो चर के साथ, संकलक को संभवतः दोनों के लिए एक रजिस्टर असाइन किया जाएगा। ये रजिस्टर वैसे भी हैं, इसलिए इसमें समय नहीं लगता है। किसी भी मामले में 2 रजिस्टर राइट और एक रजिस्टर रीड इंस्ट्रक्शन हैं।


-2

मुझे लगता है कि अधिकांश उत्तर इस बात पर विचार करने के लिए एक प्रमुख बिंदु याद कर रहे हैं कि: "क्या यह स्पष्ट है" और स्पष्ट रूप से सभी चर्चा से तथ्य यह है; नहीं ऐसा नहीं है। मैं सबसे अधिक लूप कोड में सुझाव दूंगा कि दक्षता बहुत अधिक गैर-मुद्दा है (जब तक कि आप एक मंगल लैंडर के लिए गणना नहीं कर रहे हैं), इसलिए वास्तव में एकमात्र सवाल यह है कि क्या अधिक समझदार और पठनीय और रखरखाव योग्य लगता है - इस मामले में मैं घोषणा करने की सिफारिश करूंगा। चर सामने और पाश के बाहर - यह बस इसे स्पष्ट करता है। तब आपके और मेरे जैसे लोग ऑनलाइन जाँच करने में समय बर्बाद करने की जहमत नहीं उठाएँगे कि यह वैध है या नहीं।


-6

यह सच नहीं है कि इसके ऊपरी हिस्से में उपेक्षा की जा सकती है।

भले ही शायद वे स्टैक पर एक ही स्थान पर समाप्त हो जाएंगे यह अभी भी इसे असाइन करता है। यह उस इंट के लिए मेमोरी लोकेशन को असाइन करेगा और फिर इसे} के अंत में फ्री करेगा। नहीं मुक्त भाव में अर्थ में यह 1 से sp (स्टैक पॉइंटर) को स्थानांतरित करेगा और आपके मामले में केवल एक स्थानीय चर पर विचार करने से यह बस fp (फ्रेम पॉइंटर) और सपा के बराबर होगा

संक्षिप्त उत्तर होगा: इस देखभाल को पूरा करने के लिए समान काम करना होगा।

लेकिन स्टैक कैसे आयोजित किया जाता है, इस पर अधिक पढ़ने का प्रयास करें। मेरे अंडरग्रेजुएट स्कूल में इस पर बहुत अच्छे व्याख्यान थे यदि आप यहाँ अधिक जाँच पढ़ना चाहते हैं http://www.cs.utk.edu/~plank/plank/classes/cs360/360/notes/Assembler1/lecture.html


फिर, -1 असत्य। विधानसभा में देखा गया पोस्ट पढ़ें।
बिल के

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