पॉइंटर एक वेरिएबल या स्थिरांक होता है, जिसकी वैल्यू किसी दूसरे वेरिएबल या स्थिरांक का एड्रेस होती है। पॉइंटर C का एक शक्तिशाली फीचर है, जो खतरनाक भी हो सकता है। पॉइंटर समझना मुश्किल हो सकता है।
int i = 5;
जब हम किसी वेरिएबल को कोई वैल्यू असाइन करते हैं, तो उसके बाद हम दो सवाल पूछ सकते हैं।
आप कौन हैं?
मैं i हूँ।
आपके पास क्या है?
मेरे पास पूर्णांक 5 है।
एक तीसरा सवाल भी है जो हम पूछ सकते हैं।
आप कहाँ हैं?
मैं 0x6dfed4 पर हूँ।
अरे, क्या?
वेरिएबल और स्थिरांक अपनी वैल्यू मेमोरी में एक जगह रखते हैं। मेमोरी की इस जगह को उस वेरिएबल या स्थिरांक का एड्रेस कहा जाता है।
किसी वेरिएबल का एड्रेस पाने के लिए हम एड्रेस ऑपरेटर इस्तेमाल करते हैं, जो & है।
int i = 5;
int *i_address = &i;
रुकिए, i_address से पहले वह * क्या मतलब रखता है? क्या इसका मतलब गुणा नहीं होता?
किसी टाइप के बाद * और फिर कोई आइडेंटिफायर लिखना, उस टाइप के वेरिएबल का पॉइंटर घोषित करने का तरीका है।
* को इनडायरेक्शन ऑपरेटर कहा जाता है, क्योंकि यह उस वैल्यू की ओर परोक्ष रूप से इशारा करता है जिसका एड्रेस पॉइंटर के पास होता है।
ठीक है, लेकिन सिर्फ किसी दूसरे वेरिएबल का एड्रेस रखने के लिए एक वेरिएबल क्यों बनाएँ?
और हमें किसी वेरिएबल का एड्रेस जानने की ज़रूरत ही क्यों पड़ती है?
बात यह है कि जब वेरिएबल किसी ऐरे में होते हैं, तो हमें उनकी जगह की परवाह करनी पड़ती है।
जब हम ऐसे ऐरे बनाते हैं
int my_ints[] = {1, 2};
तो my_ints की वैल्यू वास्तव में उसका ही एड्रेस होती है, और यह एड्रेस उसके पहले एलिमेंट के एड्रेस के समान भी होता है।
उसके दूसरे एलिमेंट का एड्रेस, पहले एलिमेंट के एड्रेस में उस सिस्टम पर int का आकार जोड़कर मिलेगा।
इसे बेहतर ढंग से देखने के लिए, नीचे दिए गए कोड को चलाकर देखिए
#include <stdio.h>
int main() {
// my_int's value is its address
int my_ints[] = {1, 2};
// since my_ints is already an address
// we don't need the address operator to make a pointer from it.
// a pointer to my_ints holds the address of my_ints
int *my_ptr = my_ints;
// the address of my_ints and the value of my_ptr are the same
printf("my_ints is %p. my_ptr is %p.\n", my_ints, my_ptr);
// the address of the first element is the same as the values in the line above
printf("&my_ints[0] is %p. my_ptr + 0 is %p.\n", &my_ints[0], my_ptr + 0);
// the address of the second element has increased by the size of an int
printf("&my_ints[1] is %p. my_ptr + 1 is %p.\n", &my_ints[1], my_ptr + 1); //
return 0;
}
पिछला उदाहरण दिखाता है कि ऐरे इंडेक्सिंग 0 से क्यों शुरू होती है, 1 से नहीं।
ऐरे में इंडेक्स लगाने पर इंडेक्स को टाइप के आकार से गुणा किया जाता है और उसे ऐरे के शुरुआती एड्रेस में जोड़ा जाता है।
चूँकि पहला एलिमेंट ऐरे की शुरुआत पर ही होता है, पहला एलिमेंट पाने के लिए हम टाइप को 0 से गुणा करके शुरुआती एड्रेस में जोड़ते हैं।
दूसरा एलिमेंट पाने के लिए हम टाइप को 1 से गुणा करके शुरुआती एड्रेस में जोड़ते हैं।
अगर टाइप की लंबाई चार बाइट है, तो दूसरे एलिमेंट का एड्रेस पहले एलिमेंट से चार बाइट आगे होगा।
यही वजह है कि पॉइंटर हमेशा किसी न किसी टाइप का घोषित किया जाता है।
अगर किसी पॉइंटर को यह पता न हो कि वह किस टाइप की ओर इशारा करता है, तो उसमें 1 जोड़ते समय वह यह नहीं जान पाएगा कि कितने बाइट आगे बढ़ना है।
पॉइंटर में 1 जोड़ने का मतलब हमेशा यह नहीं होता कि हम एड्रेस में एक बाइट जोड़ रहे हैं।
इसका मतलब है कि हम उसके टाइप की लंबाई (बाइट में) का 1 गुना जोड़ रहे हैं।
लेकिन रुकिए, अभी और भी बाकी है! आपको तो यह नहीं लगा था कि * की बात यहीं खत्म हो गई?
अब जब हम समझ गए हैं कि ऐरे इंडेक्सिंग के साथ एड्रेस कैसे काम करते हैं, तो अगर हम किसी एड्रेस पर रखी वैल्यू हासिल न कर सकें, तो इससे क्या फायदा?
यह हम कैसे करें?
किसी एड्रेस पर रखी वैल्यू पाने के लिए हम इनडायरेक्शन ऑपरेटर इस्तेमाल करते हैं, जो, जैसा कि आपको याद होगा, * है।
इस स्थिति में इनडायरेक्शन ऑपरेटर को कभी-कभी डीरेफरेंस ऑपरेटर भी कहा जाता है, क्योंकि यह उस वैल्यू को डीरेफरेंस करता है जिसका एड्रेस पॉइंटर के पास है।
डीरेफरेंस करते समय इसे डीरेफरेंस ऑपरेटर कहने से यह पॉइंटर घोषित करने वाले इनडायरेक्शन ऑपरेटर से अलग पहचाना जाता है, लेकिन तकनीकी रूप से दोनों जगह * का नाम इनडायरेक्शन ऑपरेटर ही है।
अरे! मज़ाक कर रहे हैं क्या?
* के कितने इस्तेमाल हैं?!
गुणा, पॉइंटर घोषित करना, और अब डीरेफरेंसिंग!
यह तो उलझन भरा है!
और डीरेफरेंसिंग है भी क्या?
एक उदाहरण दिखाता है कि डीरेफरेंस करने के लिए इनडायरेक्शन ऑपरेटर कैसे इस्तेमाल किया जाता है
#include <stdio.h>
int main() {
// define the variable
int my_ints[] = {1, 2};
// define the pointer from the address of the variable
int *my_ptr = my_ints;
// prints: my_ints[0] is 1. *(my_ptr + 0) is 1.
printf("my_ints[0] is %u. *(my_ptr + 0) is %u.\n", my_ints[0], *(my_ptr + 0));
// prints: my_ints[1] is 2. *(my_ptr + 1) is 2.
printf("my_ints[1] is %u. *(my_ptr + 1) is %u.\n", my_ints[1], *(my_ptr + 1));
return 0;
}
पॉइंटर अर्थमैटिक में गोल ब्रैकेट लगाने से इरादे साफ रखने में मदद मिलती है। नीचे दिया गया उदाहरण वह नहीं छाप सकता जो प्रोग्रामर चाहता था
#include <stdio.h>
int main() {
int my_ints[] = {1, 42};
int *my_ptr = my_ints;
// prints: my_ints[0] is 1. *++my_ptr is 42.
printf("my_ints[0] is %u. *++my_ptr is %u.\n", my_ints[0], *++my_ptr);
// prints: my_ints[1] is 43. ++*my_ptr++ is 43.
printf("my_ints[1] is %u. ++*my_ptr++ is %u.\n", my_ints[1], ++*my_ptr);
return 0;
}
दोनों * और ++ की प्राथमिकता समान है, लेकिन इनका मूल्यांकन दाएँ से बाएँ होता है।
पहली लाइन में my_ptr को पहले बढ़ाकर दूसरे एलिमेंट पर ले जाया जाता है, और उसके बाद उसे डीरेफरेंस किया जाता है।
दूसरी लाइन में my_ptr, जिसे पहली लाइन में बढ़ाकर दूसरे एलिमेंट पर ले जाया गया था, पहले डीरेफरेंस किया जाता है, और फिर डीरेफरेंस की गई वैल्यू को बढ़ाया जाता है।
अगर हम यह लाइन ऊपर के उदाहरण में जोड़ दें
// prints: my_ints[1] is 43. *++my_ptr is (some garbage value).
printf("my_ints[1] is %u. *++my_ptr is %u.\n", my_ints[1], *++my_ptr);
उस लाइन में हमने पॉइंटर को बढ़ाकर ऐरे से आगे पहुँचा दिया है।
हमने ऐरे की सीमा लाँघ ली है और हम मेमोरी में अगले चार बाइट की ओर इशारा कर रहे हैं, जिन्हें पॉइंटर एक int मान लेता है, लेकिन वास्तव में वे बाइट किसी दूसरे वेरिएबल या वेरिएबलों के हो सकते हैं।
वे चार बाइट किसी double का हिस्सा हो सकते हैं, या चार char हो सकते हैं।
समस्या यह है कि हमें अपने पॉइंटर से उन्हें देखना भी नहीं चाहिए।
हम यह भी लिख सकते थे
// prints: my_ints[2] is (some garbage value).
printf("my_ints[2] is %u", my_ints[2]);
चूँकि ऐरे इंडेक्सिंग पॉइंटर से ही लागू की जाती है, ऐरे की सीमा लाँघने का वही खतरा यहाँ भी है।
और चूँकि स्ट्रिंग char का ऐरे होती है, पॉइंटर का इस्तेमाल स्ट्रिंग के साथ भी किया जा सकता है।
स्ट्रिंग अक्सर पॉइंटर सिंटैक्स से घोषित की जाती है, कुछ इस तरह
char *my_string = "This is my string";
// instead of
// char my_string[] = "This is my string";
लेकिन एक सवाल। हम स्टार को आइडेंटिफायर के पास क्यों लगाते हैं? टाइप के पास क्यों नहीं, जबकि यह उसी टाइप का पॉइंटर है? क्योंकि यह
int* my_ptr1, my_ptr2_that_is_really_an_int;
int के दो पॉइंटर घोषित नहीं करता, बल्कि int का एक पॉइंटर और एक int घोषित करता है।
int के दो पॉइंटर घोषित करने के लिए ऐसा लिखना होगा
int *my_ptr1, *my_ptr2;
तो उलझन कम करने के लिए * को आइडेंटिफायर के पास रखना अच्छा है।
या हर लाइन में सिर्फ एक ही आइडेंटिफायर घोषित कीजिए।
और अगर * आपके लिए कम नहीं पड़ा, तो स्वागत है...
स्ट्रिंग के ऐरे अक्सर ऐसे दिखाए जाते हैं
#include <stdio.h>
void print_strings(char **passed_in_strings) {
printf("%s\n", *passed_in_strings);
printf("%s\n", *(passed_in_strings + 1));
}
int main() {
char *my_strings[] = {"One string", "Two string"};
print_strings(my_strings);
return 0;
}
यह शायद साफ न लगे, लेकिन स्ट्रिंग का यह ऐरे char ऐरे के ऐरे का पॉइंटर के रूप में घोषित और इनिशियलाइज़ किया जाता है।
जब char ऐरे का यह ऐरे print_strings() में पास किया जाता है, तो सिग्नेचर इसे char के पॉइंटर का पॉइंटर बताता है।
तो हमें कैसे पता चलेगा कि char ** वास्तव में char ऐरे के पॉइंटर के ऐरे का पॉइंटर है, और किसी एक अकेले char के पॉइंटर का पॉइंटर नहीं?
पता नहीं चलता।
यह उन कई चीज़ों में से एक है जो पॉइंटर के इस्तेमाल को उलझन भरा बना देती हैं। नीचे दिया गया कोड शायद कंपाइल हो जाए
#include <stdio.h>
void print_strings(char **passed_in_strings) {
// prints: A (followed by gibberish)
printf("%s\n", *passed_in_strings);
// prints gibberish
printf("%s\n", *(passed_in_strings + 1));
}
int main() {
char my_char = 'A';
char *my_ptr = &my_char;
char **my_ptr_to_ptr = &my_ptr;
print_strings(my_ptr_to_ptr);
return 0;
}
लेकिन print_strings() का इस्तेमाल वैसा नहीं हो रहा जैसा सोचा गया था।
अलग सिग्नेचर इस्तेमाल करने से भी फायदा नहीं होता। सिग्नेचर बदलकर ऐसा करने पर
void print_strings(char *passed_in_strings[]) {
// code snipped
}
कोड शायद फिर भी कंपाइल हो जाए और फिर भी बेतुका आउटपुट छापे।
लेकिन किसी एक अकेले char के पॉइंटर का पॉइंटर पास करने में वास्तव में कोई फायदा नहीं है।
जब भी आपको char ** दिखे, तो ज़्यादातर मौकों पर यह char ऐरे के पॉइंटर के ऐरे का पॉइंटर होता है।
हमने फंक्शन में पॉइंटर पास होते देखा है। लेकिन फंक्शन में पॉइंटर पास करना ही क्यों?
C में फंक्शन को आर्गुमेंट वैल्यू के रूप में पास किए जाते हैं। इसका मतलब है कि वैल्यू आर्गुमेंट से कॉपी की जाती है, और वह कॉपी फंक्शन के अंदर ही सीमित रहती है। जब फंक्शन खत्म होता है, तो कॉपी की गई वैल्यू भी खत्म हो जाती है।
चलिए देखते हैं कि जब हम किसी फंक्शन के अंदर वैल्यू बदलने की कोशिश करते हैं, तो क्या होता है
#include <stdio.h>
void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
//prints: In 'swap', a is 4 and b is 3.
printf("In 'swap', a is %d and b is %d.\n", a, b);
}
int main() {
int a = 3, b = 4;
swap(a, b);
// prints: After 'swap', a is 3 and b is 4
printf("After 'swap', a is %d and b is %d.\n", a, b);
return 0;
}
ऐसा इसलिए होता है कि भले ही वेरिएबलों के नाम एक जैसे हैं, swap() के अंदर वाले a और b, main() वाले a और b नहीं हैं।
वे a और b की वैल्यू की कॉपियाँ हैं।
क्या हम swap को बदलकर उसे वैसा काम करा सकते हैं जैसा हम चाहते हैं?
हाँ, बिल्कुल कर सकते हैं!
हम a और b के एड्रेस पॉइंटर के रूप में पास कर सकते हैं।
लेकिन इससे क्या फायदा, जब फंक्शन पॉइंटर की कॉपी ही बना लेता है?
फायदा तो है, क्योंकि एड्रेस की कॉपी भी वही एड्रेस रहती है।
एड्रेस को डीरेफरेंस करके हम उस मूल वैल्यू तक पहुँच सकते हैं जो main() में घोषित की गई थी।
#include <stdio.h>
// pass in pointers to a and b
void swap(int *a, int *b) {
// set temp to value at a's dereferenced address
int temp = *a;
// set a's value to value at b's dereferenced address
*a = *b;
// set b's value to temp
*b = temp;
// prints: In 'swap', a is 4 and b is 3.
printf("In 'swap', a is %d and b is %d.\n", *a, *b);
}
int main() {
int a = 3, b = 4;
swap(&a, &b);
// prints: After 'swap', a is 4 and b is 3.
printf("After 'swap', a is %d and b is %d.\n", a, b);
return 0;
}
अब swap में बहुत सारे * हैं, लेकिन अब यह वैसा ही काम करता है जैसा चाहा गया था।
इनडायरेक्शन ऑपरेटर को "एड्रेस पर मौजूद वैल्यू" के रूप में पढ़ा जा सकता है।
तो *a का अर्थ है a पर मौजूद वैल्यू।
चलिए देखें यह स्ट्रिंग के साथ कितना अच्छा काम करता है?
char के पॉइंटरहम जानते हैं कि ऐरे वेरिएबल की वैल्यू वह एड्रेस होती है जहाँ से ऐरे शुरू होता है।
और हम यह भी जानते हैं कि स्ट्रिंग को char ऐरे, char my_string[], के रूप में परिभाषित किया जा सकता है, या char के पॉइंटर, char *, के रूप में।
इन दोनों में क्या फर्क है?
इस कोड में हम describe_me() को एक char * आर्गुमेंट पास करते हैं।
adjective आर्गुमेंट char ऐरे के रूप में परिभाषित है और फंक्शन उसे char के पॉइंटर के रूप में स्वीकार करता है।
#include <stdio.h>
#include <string.h>
// takes pointer to char
void describe_me(char *adjective){
strcpy(adjective, "evil");
// prints: I am evil.
printf("I am %s.\n", adjective);
}
int main() {
// defined as a char array
char adjective[] = "good";
describe_me(adjective);
// prints: I am really evil.
printf("I am really %s.\n", adjective);
return 0;
}
हम adjective में स्थायी बदलाव कर पाते हैं, जो दिखाता है कि परिभाषित char ऐरे को char के पॉइंटर के रूप में स्वीकार किया जा सकता है।
अब चलिए adjective को सीधे char के पॉइंटर के रूप में परिभाषित करके देखते हैं।
#include <stdio.h>
#include <string.h>
void describe_me(char *adjective){
// segmentation fault on this line when program is executed
strcpy(adjective, "evil");
printf("I am %s.\n", adjective);
}
int main() {
char *adjective = "good";
describe_me(adjective);
printf("I am really %s.\n", adjective);
return 0;
}
याद है, पॉइंटर कितने शक्तिशाली होते हैं, लेकिन खतरनाक भी हो सकते हैं?
लेकिन यह काम क्यों नहीं करता?
ऐरे तो पॉइंटर है, और पॉइंटर ने char ऐरे के साथ काम किया था!
वजह यह है कि पहले adjective को char ऐरे के रूप में परिभाषित किया गया था, जो main() का ही एक वेरिएबल है।
अब adjective को स्ट्रिंग लिटरल के रूप में परिभाषित किया गया है, यानी उसके chars रीड-ओनली स्टैटिक मेमोरी में लिखे जाते हैं, जो पूरे प्रोग्राम की होती है।
दोनों पॉइंटर हैं, लेकिन अलग-अलग चीज़ों की ओर इशारा करते हैं।
किसी स्ट्रिंग लिटरल की वैल्यू बदलने की कोशिश करना, 5 की लिटरल वैल्यू बदलने की कोशिश करने जैसा है।
यह मान्य नहीं है।
तो समस्या वास्तव में यह नहीं है कि यह पॉइंटर है।
समस्या यह है कि यह किसकी ओर इशारा कर रहा है।