c++ - एक सूचक का मतलब "dereferencing" क्या है?




pointers dereference (4)

मूल शब्दावली की समीक्षा

यह आम तौर पर काफी अच्छा होता है - जब तक कि आप प्रोग्रामिंग असेंबली नहीं कर रहे हों - एक पॉइंटर पर एक संख्यात्मक मेमोरी एड्रेस होता है, जिसमें 1 की प्रक्रिया की स्मृति में दूसरे बाइट का जिक्र होता है, 2 तीसरा, 3 चौथा और इसी तरह ....

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

जब आप उस सूचकांक में डेटा / मान को एक्सेस करना चाहते हैं जो पॉइंटर इंगित करता है - उस संख्यात्मक अनुक्रमणिका के साथ पते की सामग्री - तो आप पॉइंटर को अस्वीकार करते हैं।

अलग-अलग कंप्यूटर भाषाओं में संकलक या दुभाषिया को बताने के लिए अलग-अलग नोटेशन होते हैं जिन्हें आप अब इंगित करने के लिए मूल्य में रुचि रखते हैं - मैं नीचे सी और सी ++ पर ध्यान केंद्रित करता हूं।

एक सूचक परिदृश्य

सी में विचार करें, नीचे दिए गए सूचक जैसे सूचक ...

const char* p = "abc";

... अक्षरों को 'ए', 'बी', 'सी', और 0 बाइट अक्षरों को एन्कोड करने के लिए उपयोग किए गए संख्यात्मक मानों के साथ चार बाइट, पाठ डेटा के अंत को इंगित करने के लिए स्मृति में कहीं और संग्रहीत किए जाते हैं डेटा p में संग्रहीत किया जाता है।

उदाहरण के लिए, यदि स्ट्रिंग शाब्दिक 0x1000 पता और 0x2000 पर 32-बिट पॉइंटर पर होता है, तो स्मृति सामग्री होगी:

Memory Address (hex)    Variable name    Contents
1000                                     'a' == 97 (ASCII)
1001                                     'b' == 98
1002                                     'c' == 99
1003                                     0
...
2000-2003               p                1000 hex

ध्यान दें कि पता 0x1000 के लिए कोई परिवर्तनीय नाम / पहचानकर्ता नहीं है, लेकिन हम अप्रत्यक्ष रूप से स्ट्रिंग अक्षर का संदर्भ दे सकते हैं जो उसके पते को संग्रहीत करने वाले पॉइंटर का उपयोग कर: p

सूचक को संदर्भित करना

वर्णों को इंगित करने के लिए p पॉइंट्स को संदर्भित करने के लिए, हम इन नोटिशन में से किसी एक का उपयोग करके p अस्वीकार करते हैं (फिर से, सी के लिए):

assert(*p == 'a');  // The first character at address p will be 'a'
assert(p[1] == 'b'); // p[1] actually dereferences a pointer created by adding
                     // p and 1 times the size of the things to which p points:
                     // In this case they're char which are 1 byte in C...
assert(*(p + 1) == 'b');  // Another notation for p[1]

आप बिंदुओं के माध्यम से पॉइंटर्स को भी स्थानांतरित कर सकते हैं, जैसे ही आप जाते हैं उन्हें अस्वीकार कर सकते हैं:

++p;  // Increment p so it's now 0x1001
assert(*p == 'b');  // p == 0x1001 which is where the 'b' is...

यदि आपके पास कुछ डेटा है जिसे लिखा जा सकता है, तो आप इस तरह की चीजें कर सकते हैं:

int x = 2;
int* p_x = &x;  // Put the address of the x variable into the pointer p_x
*p_x = 4;       // Change the memory at the address in p_x to be 4
assert(x == 4); // Check x is now 4

ऊपर, आपको संकलन समय पर जाना होगा कि आपको x नामक एक चर की आवश्यकता होगी, और कोड संकलक से यह कहने के लिए कहता है कि इसे कहां रखा जाना चाहिए, यह सुनिश्चित करना कि पता &x माध्यम से उपलब्ध होगा।

एक संरचना डेटा सदस्य को संदर्भित करना और एक्सेस करना

सी में, यदि आपके पास एक चर है जो डेटा सदस्यों के साथ संरचना के लिए सूचक है, तो आप -> dereferencing ऑपरेटर का उपयोग करके उन सदस्यों तक पहुंच सकते हैं:

typedef struct X { int i_; double d_; } X;
X x;
X* p = &x;
p->d_ = 3.14159;  // Dereference and access data member x.d_
(*p).d_ *= -1;    // Another equivalent notation for accessing x.d_

बहु-बाइट डेटा प्रकार

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

तो, थोड़ा और जटिल उदाहरण देख रहे हैं:

double sizes[] = { 10.3, 13.4, 11.2, 19.4 };
double* p = sizes;
assert(p[0] == 10.3);  // Knows to look at all the bytes in the first double value
assert(p[1] == 13.4);  // Actually looks at bytes from address p + 1 * sizeof(double)
                       // (sizeof(double) is almost always eight bytes)
assert(++p);           // Advance p by sizeof(double)
assert(*p == 13.4);    // The double at memory beginning at address p has value 13.4
*(p + 2) = 29.8;       // Change sizes[3] from 19.4 to 29.8
                       // Note: earlier ++p and + 2 here => sizes[3]

गतिशील आवंटित स्मृति के लिए पॉइंटर्स

कभी-कभी आप नहीं जानते कि जब तक आपका प्रोग्राम चल रहा है तब तक आपको कितनी मेमोरी की आवश्यकता होगी और देखेगा कि उस पर कौन सा डेटा फेंक दिया गया है ... फिर आप गतिशील रूप से malloc का उपयोग करके स्मृति आवंटित कर सकते हैं। पते को एक सूचक में स्टोर करना आम बात है ...

int* p = malloc(sizeof(int)); // Get some memory somewhere...
*p = 10;            // Dereference the pointer to the memory, then write a value in
fn(*p);             // Call a function, passing it the value at address p
(*p) += 3;          // Change the value, adding 3 to it
free(p);            // Release the memory back to the heap allocation library

सी ++ में, स्मृति आवंटन सामान्य रूप से new ऑपरेटर के साथ किया जाता है, और delete साथ विलोपन:

int* p = new int(10); // Memory for one int with initial value 10
delete p;

p = new int[10];      // Memory for ten ints with unspecified initial value
delete[] p;

p = new int[10]();    // Memory for ten ints that are value initialised (to 0)
delete[] p;

नीचे सी ++ स्मार्ट पॉइंटर्स भी देखें।

पते खोना और लीक करना

अक्सर एक संकेतक एकमात्र संकेत हो सकता है कि स्मृति में कुछ डेटा या बफर मौजूद है। यदि उस डेटा / बफर के चल रहे उपयोग की आवश्यकता है, या स्मृति को लीक करने से बचने के लिए free() या delete करने की क्षमता है, तो प्रोग्रामर को सूचक की एक प्रति पर काम करना चाहिए ...

const char* p = asprintf("name: %s", name);  // Common but non-Standard printf-on-heap

// Replace non-printable characters with underscores....
for (const char* q = p; *q; ++q)
    if (!isprint(*q))
        *q = '_';

printf("%s\n", p); // Only q was modified
free(p);

... या सावधानीपूर्वक किसी भी बदलाव के उलट को व्यवस्थित करें ...

const size_t n = ...;
p += n;
...
p -= n;  // Restore earlier value...

सी ++ स्मार्ट पॉइंटर्स

सी ++ में, पॉइंटर्स को स्टोर और प्रबंधित करने के लिए स्मार्ट पॉइंटर ऑब्जेक्ट्स का उपयोग करना सबसे अच्छा अभ्यास है, जब स्मार्ट पॉइंटर्स के विनाशक दौड़ते हैं तो उन्हें स्वचालित रूप से हटा दिया जाता है। चूंकि सी ++ 11 मानक लाइब्रेरी दो, unique_ptr प्रदान करता है जब आवंटित ऑब्जेक्ट के लिए एक ही मालिक होता है ...

{
    std::unique_ptr<T> p{new T(42, "meaning")};
    call_a_function(p);
    // The function above might throw, so delete here is unreliable, but...
} // p's destructor's guaranteed to run "here", calling delete

... और शेयर स्वामित्व के लिए shared_ptr ( संदर्भ गिनती का उपयोग कर) ...

{
    std::shared_ptr<T> p(new T(3.14, "pi"));
    number_storage.may_add(p); // Might copy p into its container
} // p's destructor will only delete the T if number_storage didn't copy

नल पॉइंटर्स

सी, nullptr और 0 - और इसके अतिरिक्त सी ++ nullptr - यह इंगित करने के लिए उपयोग किया जा सकता है कि एक सूचक वर्तमान में एक चर के स्मृति पते को नहीं रखता है, और इसे पॉइंटर अंकगणित में संदर्भित या उपयोग नहीं किया जाना चाहिए। उदाहरण के लिए:

const char* p_filename = NULL; // Or "= 0", or "= nullptr" in C++
char c;
while ((c = getopt(argc, argv, "f:")) != EOF)
    switch (c) {
      case f: p_filename = optarg; break;
    }
if (p_filename)  // Only NULL converts to false
    ...   // Only get here if -f flag specified

सी और सी ++ में, जैसे अंतर्निहित संख्यात्मक प्रकार 0 डिफ़ॉल्ट नहीं होते हैं, न ही false bools हैं, पॉइंटर्स हमेशा bools पर सेट नहीं होते हैं। ये सभी 0 / झूठी / NULL पर सेट होते हैं जब वे static चर या (सी ++ केवल) स्थिर वस्तुओं या उनके आधार के प्रत्यक्ष या अप्रत्यक्ष सदस्य चर होते हैं, या शून्य प्रारंभिक (जैसे new T(); और new T(x, y, z); पॉइंटर्स सहित टी के सदस्यों पर शून्य-प्रारंभिक प्रदर्शन करें, जबकि new T; नहीं करता है)।

इसके अलावा, जब आप पॉइंटर में 0 , NULL और nullptr असाइन करते हैं तो पॉइंटर में बिट्स आवश्यक रूप से सभी रीसेट नहीं होते हैं: पॉइंटर में हार्डवेयर स्तर पर "0" नहीं हो सकता है, या आपके वर्चुअल एड्रेस स्पेस में पता 0 का संदर्भ लें। कंपाइलर को इसके अलावा कुछ और स्टोर करने की इजाजत है, लेकिन जो कुछ भी करता है - यदि आप साथ आते हैं और पॉइंटर की तुलना 0 , nullptr , nullptr या अन्य पॉइंटर से करते हैं जो उनमें से किसी को असाइन किया गया था, तो तुलना अपेक्षा की जानी चाहिए । तो, कंपाइलर स्तर पर स्रोत कोड के नीचे, "न्यूल" सी और सी ++ भाषाओं में संभावित रूप से थोड़ा "जादुई" है ...

स्मृति पते के बारे में अधिक, और आपको शायद क्यों जानने की आवश्यकता नहीं है

अधिक कड़ाई से, प्रारंभिक पॉइंटर्स NULL या एक (अक्सर virtual ) मेमोरी एड्रेस की पहचान करने वाले बिट-पैटर्न को स्टोर करते हैं।

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

उदाहरण के लिए, एक int* को एक int वैरिएबल को इंगित करने के लिए ठीक से प्रारंभ किया गया हो सकता है - एक float* पर कास्टिंग करने के बाद float* - "चरम" से एक विशिष्ट "जीपीयू" मेमोरी में एक मान का उपयोग करें, फिर एक बार फ़ंक्शन पॉइंटर पर डाला गया हो सकता है समारोह के लिए मशीन opcodes पकड़े हुए।

सी और सी ++ जैसी 3 जीएल प्रोग्रामिंग भाषाएं इस जटिलता को छिपाने लगती हैं, जैसे कि:

  • यदि कंपाइलर आपको एक चर या फ़ंक्शन पर पॉइंटर देता है, तो आप इसे मुक्त रूप से अस्वीकार कर सकते हैं (जब तक चर के रूप में परिवर्तनीय नष्ट नहीं किया जाता है) और यह संकलक की समस्या है या नहीं, उदाहरण के लिए एक विशेष CPU रजिस्टर को पहले से बहाल करने की आवश्यकता है, या एक विशिष्ट मशीन कोड निर्देश का इस्तेमाल किया

  • यदि आपको किसी सरणी में किसी तत्व के लिए पॉइंटर मिलता है, तो आप सरणी में कहीं और स्थानांतरित करने के लिए पॉइंटर अंकगणित का उपयोग कर सकते हैं, या यहां तक ​​कि सरणी के एक-अतीत के अंत को एक पता बनाने के लिए भी उपयोग कर सकते हैं जो अन्य पॉइंटर्स के साथ तुलना करने के लिए कानूनी है सरणी में (या इसी तरह पॉइंटर अंकगणित द्वारा समान एक-अतीत-अंत-अंत मूल्य में स्थानांतरित किया गया है); फिर सी और सी ++ में, यह सुनिश्चित करने के लिए संकलक पर निर्भर करता है कि यह "बस काम करता है"

  • उदाहरण के लिए विशिष्ट ओएस फ़ंक्शंस जैसे साझा मेमोरी मैपिंग आपको पॉइंटर्स दे सकता है, और वे उन पते की सीमा के भीतर "बस काम करेंगे" जो उनके लिए समझ में आता है

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

स्पष्टीकरण के साथ एक उदाहरण शामिल करें।


एक सूचक एक मूल्य के लिए "संदर्भ" है .. लाइब्रेरी कॉल नंबर की तरह एक पुस्तक का संदर्भ है। कॉल नंबर "डीरफ्रेंसिंग" भौतिक रूप से उस पुस्तक को जा रहा है और पुनर्प्राप्त कर रहा है।

int a=4 ;
int *pA = &a ;
printf( "The REFERENCE/call number for the variable `a` is %p\n", pA ) ;

// The * causes pA to DEREFERENCE...  `a` via "callnumber" `pA`.
printf( "%d\n", *pA ) ; // prints 4.. 

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


मुझे लगता है कि पिछले सभी उत्तरों गलत हैं, क्योंकि वे कहते हैं कि dereferencing का मतलब वास्तविक मूल्य तक पहुंच है। विकिपीडिया इसके बजाय सही परिभाषा देता है: https://en.wikipedia.org/wiki/Dereference_operator

यह एक सूचक चर पर काम करता है, और सूचक पते पर मूल्य के बराबर एल-वैल्यू देता है। इसे पॉइंटर "dereferencing" कहा जाता है।

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

char *p = NULL;
*p;

हमने अपने मूल्य तक पहुंच के बिना नल पॉइंटर को संदर्भित किया। या हम कर सकते हैं:

p1 = &(*p);
sz = sizeof(*p);

फिर, dereferencing, लेकिन मूल्य का उपयोग कभी नहीं। ऐसा कोड क्रैश नहीं होगा: क्रैश तब होता है जब आप वास्तव में किसी अमान्य सूचक द्वारा डेटा तक पहुंचते हैं। हालांकि, दुर्भाग्यवश, मानक के अनुसार, एक अमान्य सूचक को डिफ्रेंस करना एक अपरिभाषित व्यवहार है (कुछ अपवादों के साथ), भले ही आप वास्तविक डेटा को स्पर्श करने का प्रयास न करें।

तो संक्षेप में: पॉइंटर को डिफ्रेंस करने का मतलब है कि इसे डिफरेंस ऑपरेटर को लागू करना। वह ऑपरेटर सिर्फ आपके भविष्य के उपयोग के लिए एल-वैल्यू देता है।


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





dereference