फ़ंक्शन, आर्ग्युमेंट की कॉपी और स्थानीय अवस्था
फ़ंक्शन कॉल होने पर क्या बदलता है
यह प्रोग्राम transform के अंदर एक x बदलता है। क्या main का x भी बदलता है? ट्रेस पढ़ने से पहले छपने वाली दोनों संख्याओं का अनुमान लगाइए।
#include <stdio.h>
int transform(int x) {
x = x + 4;
return 2 * x;
}
int main(void) {
int x = 3;
int y = transform(x);
printf("%d %d\n", x, y);
return 0;
}फ़ंक्शन किसी गणना को एक नाम देता है। int transform(int x) में पहला int बताता है कि कॉल एक पूर्णांक लौटाती है। transform फ़ंक्शन का नाम है और int x उसका पूर्णांक पैरामीटर घोषित करता है। ब्रेस के अंदर उसकी body है। परिभाषा main से पहले दी गई है, इसलिए कॉल के स्थान पर फ़ंक्शन का नाम और पैरामीटर का प्रकार ज्ञात हैं।
transform(x) में कोष्ठक के अंदर का व्यंजक आर्ग्युमेंट है। यहाँ वह caller का x पढ़ता है। पैरामीटर वह अलग चर है जिसे फ़ंक्शन की परिभाषा घोषित करती है; आर्ग्युमेंट उसका शुरुआती मान देता है। लौटाया गया मान वह परिणाम है जो return वापस भेजता है। ये तीन भूमिकाएँ अलग हैं: दिया गया मान, उसे ग्रहण करने वाला स्थानीय चर और कॉल का परिणाम।
सामान्य int आर्ग्युमेंट मान की कॉपी के रूप में दिया जाता है (pass by value): पैरामीटर को आर्ग्युमेंट के मान की कॉपी मिलती है। कॉपी बदलने से caller का पूर्णांक चर नहीं बदलता। एक जैसा नाम दोनों चरों को एक नहीं बना देता। पैरामीटर का नाम p होता, तब भी इस कॉल से उसे 3 ही मिलता।
फ़ंक्शन में प्रवेश करते समय अवस्था के दो अलग रिकॉर्ड बनाएँ: एक caller का और दूसरा वर्तमान कॉल का। helper के चलने के दौरान caller का रिकॉर्ड सामने रखें, लेकिन उसे न बदलें; बाद में caller का कोई कथन नया मान असाइन करे, तभी उसका रिकॉर्ड बदलें।
हल किया ट्रेस 1 पैरामीटर में मान की कॉपी
- कॉल से पहले बुलाने वाला फ़ंक्शन (caller)
mainहै और उसकी अवस्थाx = 3है।yका आरंभीकरण अभी मान मिलने की प्रतीक्षा कर रहा है। transform(x)के लिए caller के वर्तमानxको पढ़ें: आर्ग्युमेंट का मान 3 है।transformमें प्रवेश करने पर अलग पैरामीटरxको 3 मिलता है। दो अलग खाने लिखें: caller मेंx = 3; बुलाए गए फ़ंक्शन (callee) मेंx = 3।transformके अंदरx = x + 4callee का 3 पढ़कर उसी पैरामीटर में 7 रखता है। caller के खाने में अभी भी 3 है।return 2 * xसे2 * 7 = 14निकलता है और यह कॉल समाप्त होती है। यह संख्या 14 लौटाता है; न 14 छापता है, न caller केxमें असाइन करता है।mainमें लौटने पर कॉल व्यंजक का मान 14 है, इसलिए रुका हुआ आरंभीकरण पूरा होता है:y = 14। अब caller की अवस्थाx = 3, y = 14है।printfcaller के दोनों चरों को लिखे गए आउटपुट क्रम में पढ़ता है। छपने वाली पंक्ति3 14है।
मुख्य जाँच यह है कि हर असाइनमेंट कहाँ होता है। x = x + 4 का कथन transform में है; int y = transform(x) का कथन main में है। दूसरा कथन लौटे मान को y में रखता है। मान लौटाना और caller के चुने गए चर में उसे रखना अलग चरण हैं। helper के समाप्त होने पर पैरामीटर का मान अपने आप caller के चर में वापस कॉपी नहीं होता।
return वर्तमान फ़ंक्शन कॉल तुरंत समाप्त कर देता है। यदि किसी if शाखा में return चलता है, तो उस कॉल में उसके बाद के कथन नहीं चलते। यहाँ के पूर्णांक helpers हर संभव निष्पादन-पथ पर मान लौटाते हैं। आउटपुट दिखाने का काम printf करता है; helper का return केवल कॉल का मान देता है।
हल किया ट्रेस 2 helper के अंदर लूप
अगला helper अपने आर्ग्युमेंट से छोटी या उसके बराबर धनात्मक विषम संख्याएँ जोड़ता है। इसे दो बार बुलाया गया है। पहले अनुमान लगाइए: क्या दूसरी कॉल पहली कॉल के योग से शुरू होगी?
#include <stdio.h>
int odd_sum(int n) {
int total = 0;
for (int k = 1; k <= n; k += 2) {
total += k;
}
return total;
}
int main(void) {
int n = 6;
int first = odd_sum(n);
int second = odd_sum(3);
printf("%d %d %d\n", n, first, second);
return 0;
}- caller में
n = 6है। पहली कॉल के पैरामीटर कोn = 6मिलता है और उसका अपना स्थानीयtotal = 0से शुरू होता है। - पहली कॉल में
k = 1की जाँच:1 <= 6सही है। 1 जोड़ने परtotal = 1; फिरk += 2सेk = 3। - पहली कॉल में
k = 3की जाँच सही है। 3 जोड़ने परtotal = 4; अपडेट के बादk = 5। - पहली कॉल में
k = 5की जाँच सही है। 5 जोड़ने परtotal = 9; अपडेट के बादk = 7। - पहली कॉल में
k = 7की जाँच:7 <= 6गलत है। body 3 बार चली और शर्त 4 बार जाँची गई। 9 लौटता है; caller मेंfirst = 9रखा जाता है। - दूसरी कॉल का आर्ग्युमेंट स्थिरांक 3 है। नए पैरामीटर में
n = 3है और इस कॉल का नया स्थानीयtotalफिर 0 से आरंभ होता है। caller काnअभी भी 6 है। - दूसरी कॉल में
k = 1की जाँच सही है। 1 जोड़ने परtotal = 1; अपडेट के बादk = 3। - दूसरी कॉल में
k = 3की जाँच सही है। 3 जोड़ने परtotal = 4; अपडेट के बादk = 5। - दूसरी कॉल में
k = 5की जाँच गलत है। body 2 बार चली और शर्त 3 बार जाँची गई। 4 लौटता है; caller मेंsecond = 4रखा जाता है। - caller की अवस्था:
n = 6, first = 9, second = 4। छपने वाली पंक्ति6 9 4है।
लूप की हर जाँच के शुरू में total में पहले से संसाधित धनात्मक विषम संख्याओं का योग है। शुरू में ऐसी कोई संख्या नहीं है, इसलिए 0 सही है। हर सफल body वर्तमान विषम k जोड़ती है; अपडेट अगली विषम संख्या तक पहुँचाता है। इससे केवल अंतिम संख्या नहीं, योग जमा करने वाले चर का अर्थ भी स्पष्ट होता है।
दिए गए inputs पर k हर बार 2 बढ़ता है और अंत में n से बड़ा हो जाता है, इसलिए लूप रुकता है। सीमा-मान odd_sum(0) पर पहली जाँच 1 <= 0 गलत है: body एक बार भी नहीं चलती और फ़ंक्शन 0 लौटाता है। helper का लूप शून्य बार चलने पर भी helper सामान्य रूप से मान लौटा सकता है।
हर नई कॉल में int total = 0 घोषणा तक पहुँचने पर स्थानीय चर total फिर आरंभ होता है। “स्थानीय” होने का अर्थ “अपने आप शून्य” नहीं है: 0 देने वाला initializer है। दूसरी कॉल का अपना आरंभ किया हुआ चर है; वह 9 से आगे नहीं बढ़ती। यहाँ total का नाम उसकी घोषणा के बाद helper की बाकी body में, लूप की body सहित, उपयोग किया जा सकता है। for के आरंभीकरण में घोषित k का नाम उसी लूप की शर्त, अपडेट और body में उपयोग होता है; लूप के बाद वह अपने scope के बाहर है। main इन दोनों helper-स्थानीय चरों का नाम लेकर उन्हें नहीं पढ़ सकता। उसे return value के माध्यम से संख्या मिलती है।
caller का n और helper का पैरामीटर n भी अलग हैं। दूसरी कॉल में स्थिरांक 3 है, इसलिए उस आर्ग्युमेंट के लिए caller का n पढ़ा भी नहीं जाता। पहली कॉल से लौटा 9 caller के first में रहता है, जबकि दूसरी कॉल अपना परिणाम निकालती है।
छोटे फ़ंक्शन जोड़कर गणना करना
एक फ़ंक्शन का लौटाया मान दूसरे का आर्ग्युमेंट हो सकता है। outer(inner(value)) पढ़ते समय पहले अंदर वाली कॉल का परिणाम निकालें, फिर वही संख्या बाहर वाली कॉल के आर्ग्युमेंट के रूप में लें। यह कॉल के लिए आवश्यक मान का क्रम है; इसका अर्थ यह नहीं कि C के असंबंधित आर्ग्युमेंट हमेशा बाएँ से दाएँ चलते हैं।
नीचे के अभ्यास में twice अपने पूर्णांक पैरामीटर को 2 से गुणा करता है और plus3 उसमें 3 जोड़ता है। दोनों गणनाओं का क्रम बदलने से परिणाम बदल सकता है। दोनों फ़ंक्शन की body को मन में मिलाने के बजाय हर कॉल का आर्ग्युमेंट और लौटाया मान अलग लिखें। ये helpers केवल अपने पूर्णांक पैरामीटर और स्थानीय गणना उपयोग करते हैं, इसलिए किसी दूसरी अवस्था में बदलाव का ट्रेस नहीं करना है।
कॉल का भरोसेमंद ट्रेस
- caller में आर्ग्युमेंट व्यंजक का वर्तमान मान पढ़ें।
- callee की अलग अवस्था शुरू करें और उसके पैरामीटर को वही मान दें।
- helper के कथन क्रम से चलाएँ; शर्तें, लूप अपडेट और स्थानीय आरंभीकरण भी लिखें।
- जिस
returnका निष्पादन हो, वहीं उस कॉल को रोकें और उसका मान लिखें। - caller का अधूरा व्यंजक पूरा करें और मान केवल उसी चर में रखें जहाँ caller का कथन कहता है।
यहाँ सभी प्रोग्राम C11, आरंभ किए गए सामान्य signed int और छोटी, निरूपित की जा सकने वाली मध्यवर्ती संख्याएँ उपयोग करते हैं। दिखाए गए helpers अपनी कॉल से पहले परिभाषित हैं। केवल दिए गए inputs और स्पष्ट रूप से बताए छोटे सीमा-मानों का ट्रेस करें; लूप का कोई सूत्र हर संभव int के लिए सुरक्षित अंकगणित की गारंटी नहीं है।
अभ्यास
हर प्रोग्राम में caller की अवस्था, हर helper को मिला आर्ग्युमेंट और लौटाया मान लिखें। हल देखने से पहले चारों प्रश्न आज़माएँ।
अभ्यास 1
caller के चर और लौटे परिणाम का मान बताइए।
#include <stdio.h>
int decrease(int p){p-=2;return p;}
int main(void) {
int z=9;
int ans=decrease(z);
printf("%d %d\n",z,ans);
return 0;
}अभ्यास 2
दोनों फ़ंक्शन-रचनाओं की तुलना कीजिए।
#include <stdio.h>
int twice(int p){return p*2;}
int plus3(int p){return p+3;}
int main(void) {
int a=twice(plus3(4));
int b=plus3(twice(4));
printf("%d %d\n",a,b);
return 0;
}अभ्यास 3
ऋणात्मक मान, शून्य और धनात्मक मान पर परिणाम बताइए।
#include <stdio.h>
int nonnegative(int p){if(p>=0){return 1;}return 0;}
int main(void) {
int a=nonnegative(-2);
int b=nonnegative(0);
int c=nonnegative(3);
printf("%d %d %d\n",a,b,c);
return 0;
}अभ्यास 4
दो कॉल का ट्रेस कीजिए और बताइए कि दूसरी बार total शून्य से क्यों शुरू होता है।
#include <stdio.h>
int sum_to(int n){int total=0;for(int k=1;k<=n;k++){total+=k;}return total;}
int main(void) {
int a=sum_to(4);
int b=sum_to(2);
printf("%d %d\n",a,b);
return 0;
}अभ्यास के हल
अभ्यास 1
- caller में शुरू में
z = 9है।decrease(z)कॉल आर्ग्युमेंट मान 9 देती है। decreaseके अंदर अलग पैरामीटरp = 9से शुरू होता है।p -= 2सेp = 7होता है।return pसे 7 लौटता है। caller मेंans = 7रखा जाता है; caller काzअभी भी 9 है।- आउटपुट पंक्ति
9 7है।
7 7 मानने पर पैरामीटर का बदलाव गलत ढंग से z में वापस कॉपी किया गया है। 9 9 मानने पर घटाव और लौटे मान को छोड़ा गया है। 7 9 में दोनों गलतियाँ हैं। हर असाइनमेंट वास्तव में जिस चर का नाम लेता है, उसी का ट्रेस करें।
अभ्यास 2
aके लिए अंदर वाली कॉलplus3(4)है। उसका पैरामीटर 4 है और वह4 + 3 = 7लौटाती है।- इसलिए बाहर वाली कॉल
twice(7)है। वह7 * 2 = 14लौटाती है, जोaमें रखा जाता है। bके लिए अंदर वाली कॉलtwice(4)है, जो 8 लौटाती है।- इसलिए बाहर वाली कॉल
plus3(8)है, जो 11 लौटाती है; यहbमें रखा जाता है। - आउटपुट पंक्ति
14 11है। हर helper अपनी कॉल के लिए नया पैरामीटर उपयोग करता है।
11 14 में a और b की फ़ंक्शन-रचनाएँ उलट दी गई हैं। 14 14 मानता है कि 3 जोड़ने और 2 से गुणा करने का क्रम बदला जा सकता है; ऐसा नहीं है, क्योंकि पहली रचना में जोड़ा गया 3 भी दुगुना होता है। 7 8 में अंदर वाली कॉल के मान रखकर बाहर वाली कॉल छोड़ दी गई है।
अभ्यास 3
- आर्ग्युमेंट -2 पर पैरामीटर
p = -2है।p >= 0गलत है, इसलिए निष्पादनreturn 0तक पहुँचता है। अतःa = 0। - आर्ग्युमेंट 0 पर
0 >= 0सही है।return 1यह कॉल तुरंत समाप्त करता है, इसलिए बाद काreturn 0नहीं चलता। अतःb = 1। - आर्ग्युमेंट 3 पर शर्त सही है और कॉल 1 लौटाती है। अतः
c = 1। - आउटपुट पंक्ति
0 1 1है। दोनों रास्ते पूर्णांक लौटाते हैं।
0 0 1 में >= को > मानकर शून्य को गलत ढंग से बाहर किया गया है। 0 0 0 मानता है कि अंतिम return पहले return को बदल देगा, लेकिन return हो चुकी कॉल समाप्त हो चुकी है। -2 0 3 आर्ग्युमेंट दोहराता है, जबकि फ़ंक्शन वर्गीकरण का मान लौटाता है।
अभ्यास 4
- पहली कॉल: पैरामीटर
n = 4, स्थानीयtotal = 0। k = 1, 2, 3, 4पर क्रमशः योग1, 3, 6, 10होते हैं। हर body के बादkमें 1 जुड़ता है।k = 5पर शर्त गलत है। body 4 बार चलती है और शर्त 5 बार जाँची जाती है।- 10 लौटता है और caller में
a = 10रखा जाता है। - दूसरी कॉल: पैरामीटर
n = 2, नया स्थानीयtotal = 0।k = 1, 2पर योग1, 3हैं;k = 3पर शर्त गलत है। body 2 बार चलती है और शर्त 3 बार जाँची जाती है। - 3 लौटता है और
b = 3रखा जाता है। आउटपुट पंक्ति10 3है।
10 13 में दूसरी बार योग गलत ढंग से 10 से शुरू किया गया है। 6 1 में k <= n होने के बावजूद अंतिम मान छोड़ दिया गया है। 15 6 में पहली असफल जाँच के मानों 5 और 3 पर भी body चला दी गई है। अपडेट अगली जाँच की तैयारी करता है; वह अगली body चलने की गारंटी नहीं है।
अपनी व्याख्या जाँचें
पूरी व्याख्या caller के चर, वर्तमान कॉल के पैरामीटर और लौटाई संख्या को अलग रखती है। helper में लूप हो तो उसकी पहली असफल जाँच भी शामिल करें। अलग C बुनियाद पुनराभ्यास में भी यही रिकॉर्ड उपयोग करें।
स्रोत टिप्पणी
C11 के अर्थ-नियमों का संदर्भ WG14 का N1570 समिति मसौदा है। आर्ग्युमेंट की कॉपी, स्थानीय आरंभीकरण, लूप के scope और लौटाए मान के लिए §§6.5.2.2, 6.2.1–6.2.4, 6.8.5.3 और 6.8.6.4 देखें।
उदाहरण से समझें
helper को गणना करने वाली मेज़ समझें। आप पर्ची पर संख्या 3 लिखकर देते हैं और अपने रिकॉर्ड में 3 बनाए रखते हैं। मेज़ अपने अलग काम वाले खाने में 3 लिखती है, उसे 7 करती है और परिणाम की पर्ची पर 14 लौटाती है। आपके रिकॉर्ड में अभी भी 3 है। यदि आप परिणाम को y के नाम से रखते हैं, तो y का मान 14 होता है।
अगली बार मेज़ नया कार्यपत्र शुरू करती है। odd_sum में लिखा निर्देश int total = 0 उस कार्यपत्र का योग शून्य से शुरू करता है। पुराने कार्यपत्र का योग दोबारा उपयोग नहीं होता। अलग खाने अलग पूर्णांक चरों को समझाते हैं; कागज़ की यह तुलना ट्रेस में मदद के लिए है, कंप्यूटर के वास्तविक भंडारण का दावा नहीं।
सूत्र और नियम
- फ़ंक्शन परिभाषा: लौटने का प्रकार, फ़ंक्शन का नाम, प्रकार सहित पैरामीटर, फिर ब्रेस में body; इन परिभाषाओं को कॉल से पहले रखें।
- आर्ग्युमेंट: caller से दिया गया मान। पैरामीटर: उसे ग्रहण करने वाला अलग चर। लौटाया मान: कॉल व्यंजक का परिणाम।
- सामान्य
intपैरामीटर को कॉपी मिलती है। उसमें असाइन करने से caller के चर में असाइन नहीं होता। return expression;व्यंजक का मान निकालता है, वर्तमान कॉल समाप्त करता है और परिणाम देता है। वह छापता नहीं है।- caller लौटाई संख्या तभी किसी चर में रखता है जब उसका अपना कथन ऐसा कहता है।
- नई कॉल में स्थानीय घोषणा तक पहुँचने पर उसका initializer उस कॉल के लिए चलता है।
int total = 0शून्य देता है; केवल स्थानीय होना पर्याप्त नहीं है। - ब्लॉक में घोषित चर केवल अपने scope में उपयोग होता है।
forके आरंभीकरण काint kउस लूप का है, उसके बाद के कथनों का नहीं। - एक-आर्ग्युमेंट वाली नेस्टेड कॉल में अंदर का लौटाया मान बाहर का आर्ग्युमेंट बनता है। इससे आर्ग्युमेंट के सामान्य बाएँ-से-दाएँ क्रम का नियम न निकालें।
- helper के लूप का ट्रेस पहले की तरह करें: शर्त, body, अपडेट, अगली शर्त; अंतिम गलत शर्त भी शामिल करें।
- हल किया ट्रेस 1 में
3 14और हल किया ट्रेस 2 में6 9 4छपता है। अंकगणित के दावे बताए गए छोटे inputs तक सीमित रखें।
इस पाठ के नोट्स
प्रगति सहेजने के लिए साइन इन करें। साइन इन