सी पॉइंटर्स: निश्चित आकार की एक सरणी की ओर इशारा करते हुए


119

यह प्रश्न वहाँ से बाहर जाने वाले सी गुरुओं के लिए जाता है:

सी में, पॉइंटर को निम्नानुसार घोषित करना संभव है:

char (* p)[10];

.. जो मूल रूप से बताता है कि यह सूचक 10 वर्णों की एक सरणी की ओर इशारा करता है। पॉइंटर को इस तरह से घोषित करने के बारे में साफ-सुथरी बात यह है कि यदि आप पी के विभिन्न आकार के सरणी के पॉइंटर को असाइन करने का प्रयास करते हैं तो आपको एक संकलन समय त्रुटि मिलेगी। यदि आप पी के लिए एक साधारण चार सूचक के मूल्य को असाइन करने का प्रयास करते हैं तो यह आपको एक संकलन समय त्रुटि भी देगा। मैंने gcc के साथ यह कोशिश की और यह ANSI, C89 और C99 के साथ काम करने लगता है।

यह मुझे ऐसा लगता है कि एक पॉइंटर घोषित करना इस तरह से बहुत उपयोगी होगा - विशेष रूप से, जब एक फ़ंक्शन को एक पॉइंटर पास करना। आमतौर पर, लोग इस तरह के फ़ंक्शन के प्रोटोटाइप को इस तरह लिखते हैं:

void foo(char * p, int plen);

यदि आप एक विशिष्ट आकार के बफर की उम्मीद कर रहे थे, तो आप बस प्लेन के मूल्य का परीक्षण करेंगे। हालाँकि, आपको इस बात की गारंटी नहीं दी जा सकती कि जो व्यक्ति आपके पास से गुजरता है, वह वास्तव में आपको उस बफर में मान्य मेमोरी लोकेशन देगा। आपको यह विश्वास करना होगा कि जिस व्यक्ति ने इस फ़ंक्शन को बुलाया है वह सही काम कर रहा है। दूसरी ओर:

void foo(char (*p)[10]);

.. आपको निर्दिष्ट आकार का एक बफ़र देने के लिए कॉलर को मजबूर करें।

यह बहुत उपयोगी लगता है, लेकिन मैंने कभी भी किसी भी कोड को इस तरह से घोषित किए गए पॉइंटर को नहीं देखा है जो मैंने कभी भी पार किया है।

मेरा प्रश्न है: क्या कोई कारण है कि लोग इस तरह संकेत नहीं देते हैं? क्या मुझे कुछ स्पष्ट नुकसान नहीं दिख रहा है?


3
नोट: चूंकि C99 के शीर्षक के अनुसार सुझाव के अनुसार सरणी निश्चित आकार की नहीं है, इसलिए 10इसे किसी भी चर के दायरे में बदला जा सकता है
MM

जवाबों:


173

आप अपनी पोस्ट में जो कह रहे हैं वह बिल्कुल सही है। मैं कहूँगा कि हर C डेवलपर बिल्कुल एक ही खोज पर आता है और ठीक उसी निष्कर्ष पर पहुँचता है जब (यदि) वे C भाषा के लिए किसी विशेष स्तर तक पहुँचते हैं।

जब आपके एप्लिकेशन क्षेत्र की बारीकियों को विशिष्ट निश्चित आकार (सरणी आकार एक संकलन-समय स्थिर है) की एक सरणी के लिए कॉल किया जाता है, तो इस तरह के एक सरणी को किसी फ़ंक्शन में पास करने का एकमात्र उचित तरीका सूचक-टू-सरणी पैरामीटर का उपयोग करके होता है

void foo(char (*p)[10]);

(C ++ भाषा में यह संदर्भ के साथ भी किया गया है

void foo(char (&p)[10]);

)।

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

typedef int Vector3d[3];

void transform(Vector3d *vector);
/* equivalent to `void transform(int (*vector)[3])` */
...
Vector3d vec;
...
transform(&vec);

इसके अलावा ध्यान दें कि उपरोक्त कोड Vector3dएक सरणी या एक प्रकार के संबंध में अपरिवर्तनीय है struct। आप Vector3dकिसी भी समय की परिभाषा को ऐरे से structपीछे और पीछे से स्विच कर सकते हैं , और आपको फ़ंक्शन की घोषणा को बदलना नहीं पड़ेगा। या तो मामले में कार्यों को "संदर्भ द्वारा" एक समग्र वस्तु प्राप्त होगी (इसके अपवाद हैं, लेकिन इस चर्चा के संदर्भ में यह सच है)।

हालाँकि, आप सरणी के इस तरीके को स्पष्ट रूप से बहुत बार उपयोग करते हुए नहीं देखेंगे, केवल इसलिए कि बहुत से लोग बल्कि एक जटिल वाक्यविन्यास से भ्रमित हो जाते हैं और बस सी भाषा की ऐसी विशेषताओं के साथ पर्याप्त रूप से सहज नहीं होते हैं ताकि उनका सही उपयोग कर सकें। इस कारण से, औसत वास्तविक जीवन में, एक सरणी को इसके पहले तत्व के लिए एक संकेतक के रूप में पारित करना एक अधिक लोकप्रिय दृष्टिकोण है। यह सिर्फ "सरल" दिखता है।

लेकिन वास्तव में, सरणी पासिंग के लिए पहले तत्व के लिए पॉइंटर का उपयोग करना एक बहुत ही आला तकनीक है, एक ट्रिक, जो एक बहुत ही विशिष्ट उद्देश्य को पूरा करती है: इसका एक और एकमात्र उद्देश्य पासिंग सरणियों को सुविधाजनक बनाना है। विभिन्न आकार (यानी रन-टाइम आकार) के । यदि आपको वास्तव में रन-टाइम आकार के सरणियों को संसाधित करने में सक्षम होने की आवश्यकता है, तो इस तरह के एक सरणी को पास करने का उचित तरीका एक सूचक द्वारा अपने पहले तत्व के लिए एक अतिरिक्त पैरामीटर द्वारा आपूर्ति की गई ठोस आकार है।

void foo(char p[], unsigned plen);

दरअसल, कई मामलों में रन-टाइम आकार के सरणियों को संसाधित करने में सक्षम होना बहुत उपयोगी है, जो विधि की लोकप्रियता में भी योगदान देता है। कई सी डेवलपर्स बस एक निश्चित आकार के सरणी को संसाधित करने के लिए कभी भी मुठभेड़ नहीं करते (या कभी नहीं पहचानते हैं), इस प्रकार उचित निश्चित आकार की तकनीक से बेखबर रहते हैं।

फिर भी, यदि सरणी का आकार तय हो गया है, तो यह एक तत्व के लिए एक संकेतक के रूप में गुजर रहा है

void foo(char p[])

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

एक और कारण जो निश्चित आकार की सरणी पासिंग तकनीक को अपनाने में बाधा उत्पन्न कर सकता है, वह है डायनामिक रूप से आवंटित किरणों के टाइप करने के लिए भोले दृष्टिकोण का प्रभुत्व। उदाहरण के लिए, यदि प्रोग्राम प्रकार के निश्चित सरणियों के लिए कहता है char[10](जैसा कि आपके उदाहरण में), एक औसत डेवलपर इस mallocतरह के सरणियों के रूप में करेगा

char *p = malloc(10 * sizeof *p);

इस ऐरे को घोषित फंक्शन में पास नहीं किया जा सकता है

void foo(char (*p)[10]);

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

char (*p)[10] = malloc(sizeof *p);

यह, निश्चित रूप से, उपरोक्त घोषित करने के लिए आसानी से पारित किया जा सकता है foo

foo(p);

और संकलक उचित प्रकार की जाँच करेगा। लेकिन फिर, यह एक अप्रस्तुत सी डेवलपर के लिए अति भ्रमित है, यही कारण है कि आप इसे "सामान्य" औसत रोजमर्रा के कोड में बहुत बार नहीं देखेंगे।


2
यह उत्तर बहुत संक्षिप्त और सूचनात्मक विवरण देता है कि कैसे आकार () सफल होता है, कैसे यह अक्सर विफल होता है, और यह हमेशा विफल रहता है। अधिकांश C / C ++ इंजीनियरों की आपकी टिप्पणियों को समझ में नहीं आता है, और इसलिए वे ऐसा कुछ सोचते हैं जिसके बजाय वे समझते हैं कि मैंने कुछ अधिक भविष्यवाणी वाली चीजों में से एक है जो मैंने कुछ समय में देखा है, और घूंघट की सटीकता का वर्णन करने की तुलना में कुछ भी नहीं है। गंभीरता से, सर। बहुत बढ़िया जवाब।
व्हेजक्रैग

मैंने सिर्फ इस उत्तर के आधार पर कुछ कोड का खंडन किया, ब्रावो और क्यू और ए दोनों के लिए धन्यवाद
पेरी

1
मैं यह जानने के लिए उत्सुक हूं कि आप constइस तकनीक के साथ संपत्ति कैसे संभालते हैं। एक const char (*p)[N]तर्क पॉइंटर के साथ संगत नहीं लगता है char table[N];। इसके विपरीत, एक साधारण char*ptr एक const char*तर्क के साथ संगत रहता है।
सियान

4
यह ध्यान रखना उपयोगी हो सकता है कि आपके सरणी के एक तत्व तक पहुँचने के लिए, आपको करने की आवश्यकता है (*p)[i]और नहीं *p[i]। उत्तरार्द्ध सरणी के आकार से कूद जाएगा, जो लगभग निश्चित रूप से नहीं है जो आप चाहते हैं। कम से कम मेरे लिए, इस वाक्यविन्यास को सीखने से, बजाय रोकथाम के, एक गलती; मैं सही कोड प्राप्त होगा तेजी से बस एक फ्लोट * गुजर रहा है।
एंड्रयू वागनर

1
हां @ मिक्की, आपने जो सुझाव दिया है वह एक constपरिवर्तनशील तत्वों की एक सरणी के लिए एक संकेतक है। और हां, यह एक सूचक से अपरिवर्तनीय तत्वों की एक सरणी से पूरी तरह से अलग है।
सियान

11

मैं एंड्रीट के उत्तर में जोड़ना चाहूंगा (यदि कोई इस पृष्ठ पर ठोकर खाता है तो इस विषय पर अधिक जानकारी की तलाश में है):

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


   int array[9];

   const int (* p2)[9] = &array;  /* Not legal unless array is const as well */

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

यह मेरी राय में एक गंभीर बाधा है और यह मुख्य कारणों में से एक हो सकता है कि लोग आमतौर पर सी में इस तरह पॉइंटर्स की घोषणा क्यों नहीं करते हैं। दूसरा तथ्य यह है कि ज्यादातर लोग यह भी नहीं जानते हैं कि आप इस तरह से एक पॉइंटर घोषित कर सकते हैं। एंड्रीटी ने बताया है।


2
यह एक संकलक विशिष्ट मुद्दा प्रतीत होता है। मैं gcc 4.9.1 का उपयोग करके डुप्लिकेट करने में सक्षम था, लेकिन 3.4.2 क्लेंग एक गैर-कांस्टेबल से कॉस्ट संस्करण में कोई समस्या नहीं थी। मैंने C11 कल्पना (मेरे संस्करण में पी 9) को पढ़ा ... भाग दो योग्य प्रकारों के बारे में बात कर रहा था) और सहमत हैं कि ऐसा प्रतीत होता है कि ये रूपांतरण अवैध हैं। हालांकि, हम व्यवहार में जानते हैं कि आप हमेशा चेतावनी के बिना अपने आप को चार * से चार कास्ट में बदल सकते हैं। IMO, clang gcc की तुलना में इसे अनुमति देने में अधिक सुसंगत है, हालाँकि मैं आपसे सहमत हूँ कि यह कल्पना इन स्वचालित रूपांतरणों में से किसी को भी निषिद्ध करने के लिए है।
डग रिचर्डसन

4

स्पष्ट कारण यह है कि यह कोड संकलित नहीं करता है:

extern void foo(char (*p)[10]);
void bar() {
  char p[10];
  foo(p);
}

किसी सरणी का डिफ़ॉल्ट प्रचार एक अयोग्य सूचक के लिए है।

इस प्रश्न को भी देखें , foo(&p)काम करना चाहिए।


3
बेशक फू (पी) काम नहीं करेगा, फू 10 तत्वों की एक सरणी के लिए एक संकेतक के लिए पूछ रहा है ताकि आपको अपने सरणी का पता पास करने की आवश्यकता हो ...
ब्रायन आर। बॉडी

9
वह "स्पष्ट कारण" कैसे है? यह स्पष्ट रूप से समझा जाता है कि फ़ंक्शन को कॉल करने का उचित तरीका है foo(&p)
चींटी

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

2

मैं इस सिंटैक्स का उपयोग अधिक प्रकार की जाँच को सक्षम करने के लिए भी करना चाहता हूँ।

लेकिन मैं यह भी मानता हूं कि पॉइंटर्स का उपयोग करने का वाक्यविन्यास और मानसिक मॉडल सरल और याद रखने में आसान है।

यहाँ कुछ और बाधाएँ हैं जो मैं देख रहा हूँ।

  • सरणी तक पहुँचने के लिए उपयोग की आवश्यकता होती है (*p)[]:

    void foo(char (*p)[10])
    {
        char c = (*p)[3];
        (*p)[0] = 1;
    }

    इसके बजाय स्थानीय पॉइंटर-टू-चार का उपयोग करना आकर्षक है:

    void foo(char (*p)[10])
    {
        char *cp = (char *)p;
        char c = cp[3];
        cp[0] = 1;
    }

    लेकिन यह सही प्रकार का उपयोग करने के उद्देश्य को आंशिक रूप से पराजित करेगा।

  • व्यू-टू-एरे को सरणी का पता असाइन करते समय ऑपरेटर के पते-इन का उपयोग करना याद रखना होगा:

    char a[10];
    char (*p)[10] = &a;

    ऑपरेटर का पता-में पूरे सरणी का पता प्राप्त होता है &a, जिसे सही प्रकार से असाइन करना है p। ऑपरेटर के बिना, aस्वचालित रूप से सरणी के पहले तत्व के पते में परिवर्तित हो जाता है, उसी तरह&a[0] , जिसमें एक अलग प्रकार है।

    चूंकि यह स्वचालित रूपांतरण पहले से ही हो रहा है, इसलिए मैं हमेशा हैरान हूं कि &यह आवश्यक है। यह &अन्य प्रकारों के चर पर उपयोग के अनुरूप है , लेकिन मुझे यह याद रखना होगा कि एक सरणी विशेष है और मुझे इसकी आवश्यकता है& सही प्रकार का पता प्राप्त करने की , भले ही पता मूल्य समान हो।

    मेरी समस्या का एक कारण यह हो सकता है कि मैंने 80 के दशक में K & R C को वापस सीखा, जिसने &अभी तक पूरे सरणियों पर ऑपरेटर का उपयोग करने की अनुमति नहीं दी थी (हालांकि कुछ संकलकों ने इसे अनदेखा किया या वाक्य रचना को सहन किया)। जो, वैसे, एक और कारण हो सकता है कि पॉइंटर्स-टू-एरे को अपनाने के लिए एक कठिन समय है: वे केवल एएनएसआई सी के बाद से ठीक से काम करते हैं, और &ऑपरेटर सीमा उन्हें बहुत अजीब करने के लिए एक और कारण हो सकता है।

  • जब typedefहै नहीं सूचक करने वाली सरणी के लिए एक प्रकार (एक आम हेडर फाइल में) बनाने के लिए इस्तेमाल किया, तो एक वैश्विक सूचक करने वाली सरणी एक और अधिक जटिल की जरूरत externफ़ाइलों में साझा करने के लिए घोषणा:

    fileA:
    char (*p)[10];
    
    fileB:
    extern char (*p)[10];

1

खैर, सीधे शब्दों में कहें तो C इस तरह से काम नहीं करता है। एक प्रकार का एक सरणी Tपहले के लिए एक सूचक के रूप में चारों ओर से गुजरता हैT , और यह सब आपको मिलता है।

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

*dst++ = *src++

नकारात्मक पक्ष यह है कि आकार का प्रबंधन आपके ऊपर है। दुर्भाग्य से, इस कर्तव्यनिष्ठा को करने में विफलता के कारण सी कोडिंग में लाखों बग भी हो गए, और / या पुरुषवादी शोषण के अवसर पैदा हुए।

C में आप जो पूछते हैं उसके करीब क्या आता है? struct (मान द्वारा) या पॉइंटर से एक (संदर्भ द्वारा)। जब तक इस ऑपरेशन के दोनों किनारों पर समान संरचना प्रकार का उपयोग किया जाता है, दोनों कोड जो संदर्भ को सौंपते हैं और इसका उपयोग करने वाले कोड डेटा के आकार के बारे में सहमति में होते हैं।

आपकी संरचना में वह डेटा हो सकता है जो आप चाहते हैं; इसमें एक अच्छी तरह से परिभाषित आकार की आपकी सरणी हो सकती है।

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


1

आप कई प्रकार के वर्णों की एक सरणी घोषित कर सकते हैं:

char p[10];
char* p = (char*)malloc(10 * sizeof(char));

किसी फ़ंक्शन को मान द्वारा एक सरणी के लिए प्रोटोटाइप है:

void foo(char* p); //cannot modify p

या संदर्भ द्वारा:

void foo(char** p); //can modify p, derefernce by *p[0] = 'f';

या सरणी सिंटैक्स द्वारा:

void foo(char p[]); //same as char*

2
यह मत भूलो कि एक निश्चित आकार के सरणी को गतिशील रूप से भी आवंटित किया जा सकता है char (*p)[10] = malloc(sizeof *p)
एनटी

यहाँ चार सरणी [] और चार * ptr के मतभेदों के बीच अधिक विस्तृत चर्चा के लिए यहाँ देखें। stackoverflow.com/questions/1807530/…
t0mm13b

1

मैं इस समाधान की सिफारिश नहीं करूंगा

typedef int Vector3d[3];

चूंकि यह इस तथ्य को अस्पष्ट करता है कि वेक्टर 3 डी में एक प्रकार है जिसके बारे में आपको पता होना चाहिए। प्रोग्रामर आमतौर पर विभिन्न आकारों के एक ही प्रकार के चर की उम्मीद नहीं करते हैं। विचार करें :

void foo(Vector3d a) {
   Vector3D b;
}

जहां sizeof a! = sizeof b


वह इसे समाधान के रूप में नहीं सुझा रहे थे। वह बस एक उदाहरण के रूप में इसका उपयोग कर रहा था।

हम्म। जैसा sizeof(a)है वैसा क्यों नहीं है sizeof(b)?
शेरेलबेक

0

शायद मुझे कुछ याद आ रहा है, लेकिन ... चूंकि सरणियाँ निरंतर संकेत हैं, मूल रूप से इसका मतलब है कि उनके लिए संकेत के आसपास से गुजरने का कोई मतलब नहीं है।

क्या आप सिर्फ इस्तेमाल नहीं कर सकते void foo(char p[10], int plen);?


4
Arrays निरंतर संकेत नहीं हैं। कृपया सरणियों पर कुछ FAQ पढ़ें।
एनटी

2
यहाँ क्या मायने रखता है (मापदंडों के रूप में अकाट्य सरणियों), तथ्य यह है कि वे निरंतर संकेत करने के लिए क्षय। पांडित्य कम करने के तरीके पर एक FAQ पढ़ें, कृपया।
फोरट्रान

-2

मेरे संकलक (vs2008) पर यह char (*p)[10]चरित्र बिंदुओं की एक सरणी के रूप में व्यवहार करता है , जैसे कि कोई कोष्ठक नहीं था, भले ही मैं सी फ़ाइल के रूप में संकलित करता हूं। इस "चर" के लिए संकलक समर्थन है? यदि ऐसा है तो इसका उपयोग न करने का एक प्रमुख कारण है।


1
-1 गलत। यह vs2008, vs2010, gcc पर ठीक काम करता है। विशेष रूप से यह उदाहरण ठीक काम करता है: stackoverflow.com/a/19208364/2333290
kotlomoy
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.