सी, पायथन, जावास्क्रिप्ट, गो, रस्ट, सॉलिडिटी जैसी भाषाओं और कंप्यूटर साइंस के बुनियादी और गणित के उन्नत सिद्धांतों को हिंदी में पढ़ने के लिए संपर्क करें। हम वेबसाइट और सॉफ्टवेयर भी बनाते हैं।
<!-- Beej's guide to C
# vim: ts=4:sw=4:nosi:et:tw=72
-->
पॉइंटर---डर से काँपिए! {#pointers}
"कार्नेगी हॉल कैसे पहुँचते हैं?" "अभ्यास से!"
---बीसवीं सदी का एक मज़ाक, जिसके रचयिता अज्ञात हैं
[i[Pointers]<]पॉइंटर सी भाषा की सबसे डरावनी चीज़ों में गिने जाते हैं। सच तो
यह है कि इस भाषा को चुनौतीपूर्ण बनाने वाली बस यही एक चीज़ है। पर क्यों?
क्योंकि ईमानदारी से कहें तो वे कीबोर्ड के रास्ते बिजली के झटके भेज सकते हैं
और आपकी बाँहों को हमेशा के लिए अपनी जगह वेल्ड कर सकते हैं, यानी 70 के
दशक की इस भाषा में कीबोर्ड पर ज़िंदगी भर बैठे रहने का श्राप!
सच में? नहीं, सच में नहीं। मैं तो बस आपको सफलता के लिए तैयार कर रहा हूँ।
आप जिस भाषा से आए हैं, उसके आधार पर हो सकता है आप रेफ़रेंस की अवधारणा
पहले से समझते हों, जहाँ कोई वेरिएबल किसी टाइप की वस्तु की ओर संकेत करता
है।
यह लगभग वैसा ही है, बस सी में हमें ज़्यादा स्पष्ट होना पड़ता है कि हम
रेफ़रेंस की बात कर रहे हैं या उस चीज़ की जिसका वह रेफ़रेंस है।
मेमोरी और वेरिएबल {#ptmem}
कंप्यूटर की मेमोरी हर तरह का डेटा रखती है, है न? वह float रखेगी, int
रखेगी, या जो भी आपके पास हो। मेमोरी को सँभालना आसान बनाने के लिए, मेमोरी के
हर बाइट की पहचान एक पूर्णांक से होती है। मेमोरी में ऊपर बढ़ते हुए ये पूर्णांक
क्रम से बढ़ते जाते हैं^[आम तौर पर। कंप्यूटिंग के इतिहास के अँधेरे गलियारों
में अपवाद ज़रूर होंगे।]। आप इसे गिने हुए डिब्बों का एक समूह समझ सकते हैं,
जहाँ हर डिब्बे में एक बाइट^[बाइट एक ऐसी संख्या है जो अधिकतम 8 बाइनरी अंकों,
यानी संक्षेप में बिट, से बनी होती है। इसका मतलब है कि दशमलव अंकों में, जैसे
दादी-नानी के ज़माने में चलते थे, यह 0 से 255 तक (दोनों सहित) की एक अहस्ताक्षरित
संख्या रख सकता है।] डेटा रखा होता है। या, अगर आप ऐसी भाषा से आए हैं जिसमें
ऐरे होते हैं, तो एक बड़े ऐरे की तरह, जिसके हर एलिमेंट में एक बाइट है। हर
डिब्बे को दर्शाने वाली संख्या उसका एड्रेस[i[Memory address]] कहलाती है।
अब, सारे डेटा टाइप सिर्फ़ एक बाइट नहीं लेते। उदाहरण के लिए, int अक्सर चार
बाइट का होता है, और float भी, पर यह सचमुच सिस्टम पर निर्भर करता है। कोई
टाइप मेमोरी के कितने बाइट इस्तेमाल करता है, यह जानने के लिए आप sizeof
ऑपरेटर का इस्तेमाल कर सकते हैं।
// %zu is the format specifier for type size_t
printf("an int uses %zu bytes of memory\n", sizeof(int));
// That prints "4" for me, but can vary by system.
: जब कोई डेटा टाइप (जैसे आपका आम ) एक बाइट से
ज़्यादा मेमोरी लेता है, तो डेटा बनाने वाले बाइट मेमोरी में हमेशा एक-दूसरे के
बगल में होते हैं। कभी वे उस क्रम में होते हैं जिसकी आप उम्मीद करते हैं, और
कभी नहीं^[बाइट जिस क्रम में आते हैं, उसे संख्या की कहते हैं।
मेमोरी के मज़ेदार तथ्य
int
एंडियननेस
जाने-पहचाने रूप हैं बिग-एंडियन (सबसे महत्त्वपूर्ण बाइट पहले) और
लिटिल-एंडियन (सबसे महत्त्वपूर्ण बाइट आख़िर में), या, जो अब कम दिखता है,
मिक्स्ड-एंडियन (सबसे महत्त्वपूर्ण बाइट कहीं और)।]। सी मेमोरी के किसी ख़ास
क्रम की गारंटी नहीं देती (यह प्लैटफ़ॉर्म पर निर्भर है), फिर भी आम तौर पर कोड
को इस तरह लिखा जा सकता है कि वह प्लैटफ़ॉर्म से स्वतंत्र रहे और आपको इन
झंझटी बाइट-क्रमों के बारे में सोचना तक न पड़े।
तो ख़ैर, अगर हम आगे बढ़ें और पॉइंटर की परिभाषा के लिए ढोल की थाप और कुछ
अशुभ संगीत बजवा लें, तो पॉइंटर एक ऐसा वेरिएबल है जो एड्रेस रखता है। इस
मौक़े पर 2001: ए स्पेस ओडिसी का शास्त्रीय संगीत कल्पना कीजिए। बा बम बा बम बा
बम बाआआआह!
ठीक है, शायद यह थोड़ा ज़्यादा नाटकीय हो गया, हाँ? पॉइंटर में कोई ख़ास रहस्य
नहीं है। वे डेटा का एड्रेस हैं। जैसे कोई int वेरिएबल मान 12 रख सकता है,
वैसे ही पॉइंटर वेरिएबल डेटा का एड्रेस रख सकता है।
इसका मतलब है कि ये सब चीज़ें एक ही बात कहती हैं, यानी एक संख्या जो मेमोरी
में एक बिंदु को दर्शाती है:
मेमोरी में इंडेक्स (अगर आप मेमोरी को एक बड़े ऐरे की तरह सोच रहे हैं)
एड्रेस
लोकेशन
मैं इन्हें एक-दूसरे की जगह इस्तेमाल करूँगा। और हाँ, मैंने लोकेशन भी इसलिए
डाल दिया क्योंकि एक ही मतलब वाले शब्द कभी ज़्यादा नहीं होते।
और पॉइंटर वेरिएबल उस एड्रेस की संख्या रखता है। ठीक वैसे जैसे कोई float
वेरिएबल 3.14159 रख सकता है।
कल्पना कीजिए कि आपके पास पोस्ट-इट® नोट्स का एक गुच्छा है, जिन पर उनके एड्रेस
के क्रम से नंबर पड़े हैं। (पहला इंडेक्स 0 पर है, अगला इंडेक्स 1 पर, और
इसी तरह।)
उनकी स्थिति दर्शाने वाली संख्या के अलावा, आप हर नोट पर अपनी पसंद की एक और
संख्या भी लिख सकते हैं। वह आपके कुत्तों की संख्या हो सकती है। या मंगल ग्रह के
चंद्रमाओं की संख्या...
...या, वह किसी दूसरे पोस्ट-इट नोट का इंडेक्स हो सकती है!
अगर आपने अपने कुत्तों की संख्या लिखी है, तो वह बस एक सामान्य वेरिएबल है। पर
अगर आपने उसमें किसी दूसरे पोस्ट-इट का इंडेक्स लिखा है, तो वह पॉइंटर है।
वह दूसरे नोट की ओर इशारा करता है!
एक और उपमा घरों के पते की हो सकती है। आपके पास कुछ ख़ास विशेषताओं वाला घर
हो सकता है, जैसे आँगन, धातु की छत, सौर ऊर्जा, वग़ैरह। या आपके पास उस घर का
पता हो सकता है। पता और घर एक चीज़ नहीं हैं। एक पूरा-का-पूरा घर है, और दूसरा
बस कुछ पंक्तियों का लिखा हुआ पाठ। पर घर का पता उस घर का पॉइंटर है। वह घर
ख़ुद नहीं है, पर वह आपको बताता है कि उसे कहाँ ढूँढ़ना है।
और यही चीज़ हम कंप्यूटर में डेटा के साथ कर सकते हैं। आपके पास कोई डेटा
वेरिएबल हो सकता है जो कोई मान रखे। और वह मान मेमोरी में किसी एड्रेस पर है।
और आप एक अलग पॉइंटर वेरिएबल से उस डेटा वेरिएबल का एड्रेस रखवा सकते हैं।
वह डेटा वेरिएबल ख़ुद नहीं है, पर घर के पते की तरह वह हमें बताता है कि उसे
कहाँ ढूँढ़ना है।
जब हमारे पास ऐसा हो, तो हम कहते हैं कि हमारे पास उस डेटा का "पॉइंटर" है। और
हम पॉइंटर का पीछा करके डेटा तक पहुँच सकते हैं।
(अभी यह ख़ास काम का नहीं लगता, पर फ़ंक्शन कॉल के साथ इस्तेमाल होने पर यह सब
अपरिहार्य हो जाता है। वहाँ पहुँचने तक मेरे साथ बने रहिए।)
तो अगर हमारे पास, मान लीजिए, एक int है और हमें उसका पॉइंटर चाहिए, तो हमें
उस int का एड्रेस पाने का कोई तरीका चाहिए, है न? आख़िर पॉइंटर तो बस डेटा का
एड्रेस रखता है। आपके विचार से int का एड्रेस पाने के लिए हम कौन-सा
ऑपरेटर इस्तेमाल करेंगे?
[i[& address-of operator]<]तो, एक चौंकाने वाले आश्चर्य के साथ, जो शायद
आपको चौंकाने जैसा ही लगेगा, प्रिय पाठक, डेटा का एड्रेस पाने के लिए हम
address-of ऑपरेटर (जो संयोग से एक एम्परसैंड है: "&") इस्तेमाल करते हैं।
एम्परसैंड।
तो एक झटपट उदाहरण के लिए, हम printf() के लिए एक नया फ़ॉर्मैट स्पेसिफ़ायर
पेश करेंगे ताकि आप पॉइंटर प्रिंट कर सकें। आप जानते ही हैं कि %d दशमलव
पूर्णांक कैसे प्रिंट करता है, हाँ? तो %p[i[printf() function-->with
pointers]] पॉइंटर प्रिंट करता है। अब, यह पॉइंटर किसी फ़ालतू संख्या जैसा
दिखेगा (और संभव है कि यह दशमलव की जगह हेक्साडेसिमल^[यानी आधार 16, जिसके अंक
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E और F हैं।] में छपे), पर वह बस
मेमोरी का वह इंडेक्स है जहाँ डेटा रखा है। (या मेमोरी का वह इंडेक्स जहाँ डेटा
का पहला बाइट रखा है, अगर डेटा कई बाइट का है।) लगभग हर परिस्थिति में, इस
परिस्थिति सहित, छपी हुई संख्या का असली मान आपके लिए महत्त्वहीन है, और मैं उसे
यहाँ सिर्फ़ address-of ऑपरेटर के प्रदर्शन के लिए दिखा रहा हूँ।
#include <stdio.h>
int main(void)
{
int i = 10;
printf("The value of i is %d\n", i);
printf("And its address is %p\n", (void *)&i);
}
ऊपर के कोड में एक कास्ट है, जहाँ हम एक्सप्रेशन &i का टाइप ज़बरदस्ती
void* बना देते हैं। ऐसा इसलिए कि कंपाइलर यहाँ चेतावनी न दे। यह सब वह चीज़
है जो हमने अभी तक नहीं सीखी, तो फ़िलहाल ऊपर के कोड में (void*) को अनदेखा
कर दीजिए और मान लीजिए कि वह वहाँ है ही नहीं।
मेरे कंप्यूटर पर यह प्रिंट करता है:
The value of i is 10
And its address is 0x7ffddf7072a4
[i[& address-of operator]>]
अगर आप जानना चाहें, तो वह हेक्साडेसिमल संख्या दशमलव (आधार 10, जैसे
दादी-नानी इस्तेमाल करती थीं) में 140,727,326,896,068 है। वह मेमोरी का वह
इंडेक्स है जहाँ वेरिएबल i का डेटा रखा है। वह i का एड्रेस है। वह i की
लोकेशन है। वह i का पॉइंटर है।
रुकिए, आपके पास 140 टेराबाइट रैम है? हाँ! आपके पास नहीं है? पर मैं
बहुत गप हाँकता हूँ; बेशक मेरे पास नहीं है (लगभग 2024)। आधुनिक कंप्यूटर
[flw[वर्चुअल मेमोरी|Virtual_memory]] नाम की एक चमत्कारी तकनीक इस्तेमाल करते
हैं, जो प्रोसेस को यह यक़ीन दिलाती है कि आपके कंप्यूटर का पूरा मेमोरी स्पेस
उसी का है, चाहे उसके पीछे कितनी भी भौतिक रैम हो। तो भले एड्रेस इतनी बड़ी
संख्या था, मेरे सीपीयू का वर्चुअल मेमोरी सिस्टम उसे किसी कम भौतिक मेमोरी
एड्रेस से जोड़ देता है। इस कंप्यूटर में 16 जीबी रैम है (फिर, लगभग 2024, पर
मैं लिनक्स चला रहा हूँ, इसलिए यह काफ़ी है)। टेराबाइट रैम? मैं एक शिक्षक हूँ,
डॉट-कॉम का अरबपति नहीं। इनमें से किसी भी बात की चिंता हममें से किसी को
नहीं करनी है, सिवाय इसके कि मैं अथाह धनी नहीं हूँ।
यह पॉइंटर इसलिए है क्योंकि यह आपको बताता है कि i मेमोरी में कहाँ है। जैसे
काग़ज़ के टुकड़े पर लिखा घर का पता बताता है कि कोई ख़ास घर कहाँ मिलेगा, वैसे ही
यह संख्या हमें बताती है कि मेमोरी में i का मान कहाँ मिलेगा। यह i की ओर
इशारा करती है।
फिर कहता हूँ, आम तौर पर हमें इस बात की परवाह नहीं होती कि एड्रेस की सटीक
संख्या क्या है। हमें बस इतना मतलब है कि वह i का पॉइंटर है।
पॉइंटर टाइप {#pttypes}
[i[Pointer types]<]तो... यह सब ठीक-ठाक है। अब आप किसी वेरिएबल का एड्रेस लेकर
उसे स्क्रीन पर सफलतापूर्वक प्रिंट कर सकते हैं। पुराने बायोडेटा के लिए कुछ तो
हुआ, है न? यहीं पर आप मेरी गर्दन पकड़कर शिष्टता से पूछते हैं कि आख़िर पॉइंटर
किस काम के हैं।
बढ़िया सवाल, और हम उस पर आएँगे, पर हमारे प्रायोजक का संदेश सुनने के बाद।
ACME ROBOTIC HOUSING UNIT CLEANING SERVICES. YOUR HOMESTEAD WILL BE
DRAMATICALLY IMPROVED OR YOU WILL BE TERMINATED. MESSAGE ENDS.
बीज की गाइड की एक और कड़ी में आपका स्वागत है। पिछली बार जब हम मिले थे, तो हम
पॉइंटर का इस्तेमाल करने के बारे में बात कर रहे थे। तो अब हम यह करने जा रहे
हैं कि एक पॉइंटर को किसी वेरिएबल में सहेज लें ताकि बाद में उसका इस्तेमाल कर
सकें। आप पॉइंटर टाइप को इससे पहचान सकते हैं कि वेरिएबल के नाम से पहले और
उसके टाइप के बाद एक तारा-चिह्न (*) होता है:
int main(void)
{
int i; // i's type is "int"
int *p; // p's type is "pointer to an int", or "int-pointer"
}
तो यहाँ हमारे पास एक वेरिएबल है जो पॉइंटर टाइप का है, और वह दूसरे int की
ओर इशारा कर सकता है। यानी वह दूसरे int का एड्रेस रख सकता है। हमें पता है
कि वह int की ओर इशारा करता है, क्योंकि वह int* टाइप का है (पढ़िए
"इंट-पॉइंटर")।
जब आप किसी पॉइंटर वेरिएबल में असाइनमेंट करते हैं, तो असाइनमेंट की दाईं ओर का
टाइप वही होना चाहिए जो पॉइंटर वेरिएबल का टाइप है। हमारी क़िस्मत से, जब आप
किसी वेरिएबल का address-of लेते हैं, तो नतीजे का टाइप उस वेरिएबल टाइप का
पॉइंटर होता है, इसलिए नीचे जैसे असाइनमेंट बिलकुल सही हैं:
int i;
int *p; // p is a pointer, but is uninitialized and points to garbage
p = &i; // p is assigned the address of i--p now "points to" i
असाइनमेंट की बाईं ओर हमारे पास पॉइंटर-टू-int (int*) टाइप का वेरिएबल है,
और दाईं ओर पॉइंटर-टू-int टाइप का एक्सप्रेशन है, क्योंकि i एक int है
(क्योंकि int का address-of आपको int का पॉइंटर देता है)। किसी चीज़ का
एड्रेस उस चीज़ के पॉइंटर में रखा जा सकता है।
समझ आया? मुझे पता है कि अभी भी यह पूरी तरह समझ नहीं आ रहा होगा, क्योंकि आपने
पॉइंटर वेरिएबल का कोई असली इस्तेमाल अभी नहीं देखा, पर हम छोटे क़दम इसलिए ले रहे
हैं कि कोई खो न जाए। तो अब चलिए आपको एंटी-address-of ऑपरेटर से मिलवाते हैं।
यह कुछ वैसा है जैसा address-of बिज़ारो वर्ल्ड में होता।[i[Pointer types]>]
डीरेफ़रेंसिंग {#deref}
[i[Dereferencing]<]पॉइंटर वेरिएबल को ऐसे सोचा जा सकता है कि वह किसी दूसरे
वेरिएबल की ओर इशारा करके उसका रेफ़रेंस दे रहा है। सी की दुनिया में शायद ही
कोई "रेफ़र करने" या "रेफ़रेंस" की बात करता सुनाई दे, पर मैं यह इसलिए बता रहा
हूँ कि इस ऑपरेटर का नाम थोड़ा और समझ में आए।
जब आपके पास किसी वेरिएबल का पॉइंटर हो (मोटे तौर पर "किसी वेरिएबल का
रेफ़रेंस"), तो आप पॉइंटर को डीरेफ़रेंस करके उस पॉइंटर के ज़रिए मूल वेरिएबल
का इस्तेमाल कर सकते हैं। (आप इसे पॉइंटर का "पॉइंटर हटाना" समझ सकते हैं, पर
कोई कभी "पॉइंटर हटाना" नहीं कहता।)
अपनी उपमा पर लौटें तो यह कुछ-कुछ घर का पता देखकर फिर उस घर में जाने जैसा है।
अब, "मूल वेरिएबल तक पहुँच पाना" से मेरा क्या मतलब है? देखिए, अगर आपके पास i
नाम का वेरिएबल है, और i का एक पॉइंटर p है, तो आप डीरेफ़रेंस किए हुए
पॉइंटर p को ठीक वैसे इस्तेमाल कर सकते हैं मानो वह मूल वेरिएबल i हो!
[i[* indirection operator]<]उदाहरण सँभालने लायक़ ज्ञान आपके पास लगभग हो चुका
है। जो आख़िरी बात जाननी बाक़ी है, वह यह है: डीरेफ़रेंस ऑपरेटर क्या है? असल में
इसे इंडायरेक्शन ऑपरेटर कहते हैं, क्योंकि आप पॉइंटर के ज़रिए मानों तक अप्रत्यक्ष
रूप से पहुँच रहे हैं। और यह फिर से तारा-चिह्न है: *। अब, इसे उस तारा-चिह्न से
मत गड्ड-मड्ड कीजिए जो आपने पहले पॉइंटर की घोषणा में इस्तेमाल किया था। वे एक ही
अक्षर हैं, पर अलग-अलग संदर्भों में उनके अर्थ अलग हैं^[बात यहीं ख़त्म नहीं होती!
यह /*comments*/ में, गुणा में, और वेरिएबल लेंथ ऐरे वाले फ़ंक्शन प्रोटोटाइप में
भी इस्तेमाल होता है! यह हर जगह वही * है, पर संदर्भ उसे अलग अर्थ देता है।]।
यह रहा एक पूरा उदाहरण:
#include <stdio.h>
int main(void)
{
int i;
int *p; // this is NOT a dereference--this is a type "int*"
p = &i; // p now points to i, p holds address of i
i = 10; // i is now 10
*p = 20; // the thing p points to (namely i!) is now 20!!
printf("i is %d\n", i); // prints "20"
printf("i is %d\n", *p); // "20"! dereference-p is the same as i!
}
याद रखिए कि p में i का एड्रेस है, जैसा आप उस जगह देख सकते हैं जहाँ हमने
8वीं पंक्ति में p को असाइन किया। इंडायरेक्शन ऑपरेटर कंप्यूटर से कहता है कि
पॉइंटर को ख़ुद इस्तेमाल करने की जगह वह वस्तु इस्तेमाल करो जिसकी ओर पॉइंटर इशारा
करता है। इस तरह हमने *p को एक तरह से i का उपनाम बना दिया।[i[Dereferencing]>][i[* indirection operator]>]
बढ़िया, पर क्यों? यह सब करने की ज़रूरत ही क्या है?
पॉइंटर को आर्ग्युमेंट के रूप में पास करना {#ptpass}
[i[Pointers-->as arguments]<]इस वक़्त आप सोच रहे होंगे कि पॉइंटर के बारे में
आपके पास ज्ञान तो बहुत है, पर उसका उपयोग बिलकुल शून्य, है न? मतलब, अगर सीधे i
कह सकते हैं तो *p का क्या फ़ायदा?
तो मेरे दोस्त, पॉइंटर की असली ताक़त तब सामने आती है जब आप उन्हें फ़ंक्शन को पास
करना शुरू करते हैं। यह इतनी बड़ी बात क्यों है? आपको शायद याद हो कि पहले आप
फ़ंक्शन को तरह-तरह के आर्ग्युमेंट पास कर सकते थे और वे ईमानदारी से पैरामीटर में
कॉपी हो जाते थे, फिर आप फ़ंक्शन के भीतर उन वेरिएबल की लोकल कॉपियों के साथ
छेड़छाड़ कर सकते थे, और फिर एक अकेला मान लौटा सकते थे।
अगर आप फ़ंक्शन से एक से ज़्यादा डेटा वापस लाना चाहें तो? मतलब, आप लौटा तो सिर्फ़
एक ही चीज़ सकते हैं, है न? अगर मैं इस सवाल का जवाब एक और सवाल से दूँ तो?
...अरे, दो सवालों से?
जब आप किसी फ़ंक्शन को आर्ग्युमेंट के रूप में पॉइंटर पास करते हैं, तो क्या
पॉइंटर की एक कॉपी उसके संबंधित पैरामीटर में रखी जाती है? बिलकुल रखी जाती है,
जनाब। याद है मैंने पहले कितनी बकबक की थी कि हर एक आर्ग्युमेंट पैरामीटर में
कॉपी होता है और फ़ंक्शन आर्ग्युमेंट की कॉपी इस्तेमाल करता है? तो यहाँ भी वही
सच है। फ़ंक्शन को पॉइंटर की एक कॉपी मिलेगी।
पर, और यही चालाक हिस्सा है: हमने पॉइंटर को पहले से ही किसी वेरिएबल की ओर इशारा
करने के लिए तैयार कर रखा होगा... और फिर फ़ंक्शन अपने पॉइंटर की कॉपी को
डीरेफ़रेंस करके मूल वेरिएबल तक वापस पहुँच सकता है! फ़ंक्शन वेरिएबल को ख़ुद
नहीं देख सकता, पर उस वेरिएबल के पॉइंटर को डीरेफ़रेंस तो बिलकुल कर सकता है!
यह ऐसा है जैसे आप किसी काग़ज़ पर घर का पता लिखें, और फिर उसे दूसरे काग़ज़ पर उतार
लें। अब आपके पास उस घर के दो पॉइंटर हैं, और दोनों आपको घर तक पहुँचाने में
बराबर अच्छे हैं।
फ़ंक्शन कॉल के मामले में, कॉपियों में से एक कॉलिंग स्कोप में बाहर किसी पॉइंटर
वेरिएबल में रखी होती है, और दूसरी फ़ंक्शन के पैरामीटर वाले पॉइंटर वेरिएबल में।
उदाहरण! चलिए अपने पुराने increment() फ़ंक्शन पर दोबारा चलते हैं, पर इस बार
इसे ऐसा बनाएँगे कि यह कॉलर में बाहर मौजूद मान को सचमुच बढ़ाए।
#include <stdio.h>
void increment(int *p) // note that it accepts a pointer to an int
{
*p = *p + 1; // add one to the thing p points to
}
int main(void)
{
int i = 10;
int *j = &i; // note the address-of; turns it into a pointer to i
printf("i is %d\n", i); // prints "10"
printf("i is also %d\n", *j); // prints "10"
increment(j); // j is an int*--to i
printf("i is %d\n", i); // prints "11"!
}
ठीक! यहाँ कुछ बातें देखने लायक़ हैं... जिनमें से सबसे अहम यह कि increment()
फ़ंक्शन आर्ग्युमेंट के रूप में एक int* लेता है। हम कॉल में उसे int* पास करते
हैं, int वेरिएबल i को address-of ऑपरेटर से int* में बदलकर। (याद रखिए,
पॉइंटर एड्रेस रखता है, इसलिए वेरिएबल के पॉइंटर बनाने के लिए हम उन्हें
address-of ऑपरेटर से गुज़ारते हैं।)
increment() फ़ंक्शन को पॉइंटर की एक कॉपी मिलती है। मूल पॉइंटर j (main()
में) और उस पॉइंटर की कॉपी p (increment() का पैरामीटर) दोनों एक ही एड्रेस की
ओर इशारा करते हैं, यानी उस एड्रेस की ओर जहाँ i का मान रखा है। (फिर उपमा से
कहें तो, दो काग़ज़ के टुकड़ों की तरह जिन पर एक ही घर का पता लिखा हो।) किसी को
भी डीरेफ़रेंस करने से आप मूल वेरिएबल i को बदल सकेंगे! फ़ंक्शन किसी दूसरे
स्कोप के वेरिएबल को बदल सकता है! वाह!
ऊपर का उदाहरण अक्सर कॉल में आर्ग्युमेंट सूची के भीतर ही address-of इस्तेमाल करके
ज़्यादा संक्षेप में लिखा जाता है:
printf("i is %d\n", i); // prints "10"
increment(&i);
printf("i is %d\n", i); // prints "11"!
सामान्य नियम के तौर पर, अगर आप चाहते हैं कि फ़ंक्शन आपकी दी हुई चीज़ को इस तरह
बदले कि आपको नतीजा दिखे, तो आपको उस चीज़ का पॉइंटर पास करना होगा।
NULL पॉइंटर {#the-null-pointer}
[i[NULL pointer]<]किसी भी पॉइंटर टाइप का कोई भी पॉइंटर वेरिएबल एक विशेष मान
NULL पर सेट किया जा सकता है। यह बताता है कि यह पॉइंटर किसी चीज़ की ओर इशारा
नहीं करता।
int *p;
p = NULL;
चूँकि वह किसी मान की ओर इशारा नहीं करता, इसलिए उसे डीरेफ़रेंस करना अनडिफ़ाइंड
बिहेवियर है, और संभवतः उसका नतीजा क्रैश होगा:
int *p = NULL;
*p = 12; // CRASH or SOMETHING PROBABLY BAD. BEST AVOIDED.
अपने रचयिता द्वारा [flw[अरबों डॉलर की भूल|Null_pointer#History]] कहे जाने के
बावजूद, NULL पॉइंटर एक अच्छा [flw[सेंटिनल मान|Sentinel_value]] है, और इस बात
का सामान्य संकेतक भी कि कोई पॉइंटर अभी इनिशियलाइज़ नहीं हुआ है।
(बेशक, दूसरे वेरिएबल की तरह, पॉइंटर भी कचरे की ओर इशारा करता है जब तक आप उसे
स्पष्ट रूप से किसी एड्रेस या NULL की ओर इशारा करने के लिए असाइन न करें।)
[i[NULL pointer]>]
पॉइंटर घोषित करने पर एक टिप्पणी {#a-note-on-declaring-pointers}
[i[Pointers-->declarations]<]पॉइंटर घोषित करने का सिंटैक्स थोड़ा अजीब हो सकता
है। इस उदाहरण को देखिए:
int a;
int b;
हम इसे एक पंक्ति में समेट सकते हैं, है न?
int a, b; // Same thing
तो a और b दोनों int हैं। कोई दिक़्क़त नहीं।
पर इसका क्या?
int a;
int *p;
क्या हम इसे एक पंक्ति में बना सकते हैं? बना सकते हैं। पर * कहाँ जाएगा?
नियम यह है कि * हर उस वेरिएबल के सामने जाता है जो पॉइंटर टाइप का है। यानी,
इस उदाहरण में *int का हिस्सा नहीं है। वह वेरिएबल p का हिस्सा है।
इसे ध्यान में रखकर हम यह लिख सकते हैं:
int a, *p; // Same thing
यह ध्यान देना ज़रूरी है कि नीचे की पंक्ति दो पॉइंटर घोषित नहीं करती:
int *p, q; // p is a pointer to an int; q is just an int.
यह ख़ास तौर पर धोखा देने वाली दिख सकती है अगर प्रोग्रामर नीचे की (वैध) पंक्ति
लिखे, जो कार्यात्मक रूप से ऊपर वाली के समान है।
int* p, q; // p is a pointer to an int; q is just an int.
तो इसे देखिए और तय कीजिए कि कौन-से वेरिएबल पॉइंटर हैं और कौन-से नहीं:
int *a, b, c, *d, e, *f, g, h, *i;
जवाब मैं फ़ुटनोट में दे रहा हूँ^[पॉइंटर टाइप के वेरिएबल a, d, f और i
हैं, क्योंकि इन्हीं के सामने * है।]।[i[Pointers-->declarations]>]
sizeof और पॉइंटर {#sizeof-and-pointers}
[i[Pointers-->with sizeof]<]थोड़ा-सा सिंटैक्स, जो उलझा सकता है और जो आपको
समय-समय पर दिख सकता है।
याद कीजिए कि sizeof एक्सप्रेशन के टाइप पर काम करता है।
int *p;
// Prints size of an 'int'
printf("%zu\n", sizeof(int));
// p is type 'int *', so prints size of 'int*'
printf("%zu\n", sizeof p);
// *p is type 'int', so prints size of 'int'
printf("%zu\n", sizeof *p);
हो सकता है आपको असली कोड में वह आख़िरी sizeof दिख जाए। बस याद रखिए कि
sizeof एक्सप्रेशन के टाइप के बारे में है, एक्सप्रेशन में मौजूद वेरिएबल के
बारे में नहीं।[i[Pointers-->with sizeof]>][i[Pointers]>]