दो आयामी सरणी आवंटित करने का अजीब तरीका?


110

एक परियोजना में, किसी ने इस लाइन को धक्का दिया:

double (*e)[n+1] = malloc((n+1) * sizeof(*e));

जो माना जाता है कि (n + 1) * (n + 1) युगल का दो आयामी सरणी बनाता है।

माना जाता है , मैं कहता हूं, क्योंकि अभी तक, किसी ने मुझसे नहीं पूछा कि यह क्या करता है, मुझे बता सकता है, और न ही यह कहां से उत्पन्न हुआ है या इसे क्यों काम करना चाहिए (जो कथित तौर पर, यह करता है, लेकिन मैं अभी तक इसे नहीं खरीद रहा हूं)।

शायद मुझे कुछ स्पष्ट याद आ रहा है, लेकिन अगर कोई मुझे ऊपर की पंक्ति में समझा सकता है तो मैं इसकी सराहना करूंगा। क्योंकि व्यक्तिगत रूप से, मुझे बहुत अच्छा लगेगा अगर हम कुछ ऐसा उपयोग करें जो हम वास्तव में समझते हैं।


15
यहां तक ​​कि अगर यह काम करता है, तो पुशर को आग दें।
मार्टिन जेम्स

22
@MartinJames क्यों? आप आसन्न मेमोरी में 2 डी सरणी कैसे आवंटित करेंगे अन्यथा? 1D सरणी में एक गड़बड़? यह 1990 की प्रोग्रामिंग है, अब हमारे पास वीएलएएस है।
लंडिन

43
रिकॉर्ड के लिए, कि है एक और गतिशील रूप से एक वास्तविक 2 डी सरणी का आवंटन का एकमात्र तरीका।
क्वेंटिन

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

15
इस समस्या को स्पष्ट करने के लिए स्पष्ट किया गया होगा / जवाब में नमूना समान मूल्यवान आयामों का उपयोग नहीं किया गया था n+1लेकिन इसके बजायdouble (*e)[rows] = malloc(columns * sizeof *e);
chux - Reinstate Monica

जवाबों:


87

चर eप्रकार के n + 1तत्वों की एक सरणी के लिए एक संकेतक है double

डीरेफेरेंस ऑपरेटर का उपयोग eआपको आधार-प्रकार देता है, eजो "प्रकार के n + 1तत्वों की सरणी double" है।

mallocकॉल बस के आधार प्रकार लेता है e(जैसा कि ऊपर बताया) और इसके द्वारा अपने आकार पलता हो जाता है, n + 1और करने के लिए है कि आकार गुजर mallocकार्य करते हैं। अनिवार्य रूप n + 1से n + 1तत्वों के सरणियों के एक सरणी को आवंटित करना double


3
@MartinJames sizeof(*e)के बराबर है sizeof(double [n + 1])। गुणा करें कि n + 1आप और आप पर्याप्त हैं।
कुछ प्रोग्रामर ने

24
@MartinJames: इसमें क्या गलत है? यह आंख-छुरा नहीं है, यह गारंटी देता है कि आवंटित पंक्तियाँ सन्निहित हैं, और आप इसे किसी अन्य 2D सरणी की तरह अनुक्रमित कर सकते हैं। मैं इस मुहावरे का अपने कोड में बहुत उपयोग करता हूं।
जॉन बोडे

3
यह स्पष्ट लग सकता है, लेकिन यह केवल वर्ग सरणियों (समान आयामों) के लिए काम करता है ।
जेन्स

18
@ जेन्स: केवल इस अर्थ में कि यदि आप n+1दोनों आयामों के लिए रखते हैं, तो परिणाम चौकोर होगा। यदि आप करते हैं double (*e)[cols] = malloc(rows * sizeof(*e));, तो परिणाम में आपके द्वारा निर्दिष्ट पंक्तियों और स्तंभों की संख्या होगी।
user2357112

9
@ user2357112 अब मैं बहुत कुछ देखूंगा। भले ही इसका मतलब आपको जोड़ना हो int rows = n+1और int cols = n+1। भगवान हमें चतुर कोड से बचाएं।
कैंडिड_ऑरेंज

56

यह वह विशिष्ट तरीका है जिसे आपको डायनामिक रूप से 2D सरणियों को आवंटित करना चाहिए।

  • eएक सरणी प्रकार की एक सरणी सूचक है double [n+1]
  • sizeof(*e)इसलिए इंगित-प्रकार का प्रकार देता है, जो एक double [n+1]सरणी का आकार है ।
  • आप n+1ऐसे सरणियों के लिए कमरा आवंटित करते हैं ।
  • आप सरणी सूचक eको सरणियों के इस सरणी में पहले सरणी में इंगित करने के लिए सेट करते हैं।
  • यह आप का उपयोग करने की अनुमति देता eके रूप में e[i][j]2 डी सरणी में पहुँच अलग-अलग आइटम के लिए।

व्यक्तिगत रूप से मुझे लगता है कि इस शैली को पढ़ना बहुत आसान है:

double (*e)[n+1] = malloc( sizeof(double[n+1][n+1]) );

12
आपकी सुझाई शैली से असहमत होने के अलावा अच्छा उत्तर, शैली को तरजीह देना ptr = malloc(sizeof *ptr * count)
chux -

अच्छा जवाब, और मुझे आपकी पसंदीदा शैली पसंद है। एक मामूली सुधार यह इंगित करने के लिए हो सकता है कि आपको इसे इस तरह करने की आवश्यकता है क्योंकि उन पंक्तियों के बीच पैडिंग हो सकती है जिन्हें ध्यान में रखना आवश्यक है। (कम से कम, मुझे लगता है कि यही कारण है कि आपको इसे इस तरह से करने की आवश्यकता है।) (मुझे बताएं कि क्या मैं गलत हूं।)
दाविदबक

2
@davidbak यही बात है। सरणी सिंटैक्स केवल स्व-दस्तावेजीकरण कोड है: यह कहता है "2 डी सरणी के लिए कमरा आवंटित करें" स्रोत कोड के साथ ही।
लुंडिन

1
@davidbak नोट: टिप्पणी का एक मामूली नुकसान malloc(row*col*sizeof(double))तब होता है जब row*col*sizeof()ओवरफ्लो होता है , लेकिन ऐसा नहीं sizeof()*row*colहोता है। (उदाहरण के लिए पंक्ति, कोल हैं int)
chux -

7
@davidbak: sizeof *e * (n+1)बनाए रखना आसान है; अगर आप कभी भी आधार प्रकार (से बदलने का फैसला doubleकरने के लिए long doubleउदाहरण के लिए,), तो आप केवल की घोषणा को बदलने की जरूरत e; आपको कॉल sizeofमें अभिव्यक्ति को संशोधित करने की आवश्यकता नहीं है malloc(जो समय बचाता है और आपको इसे एक स्थान पर बदलने से बचाता है, लेकिन दूसरे को नहीं)। sizeof *eहमेशा आपको सही आकार देगा।
जॉन बोडे

39

यह मुहावरा स्वाभाविक रूप से 1D सरणी आवंटन से बाहर आता है। चलो कुछ मनमाने प्रकार के 1D सरणी आवंटित करने के साथ शुरू करते हैं T:

T *p = malloc( sizeof *p * N );

सरल, सही? अभिव्यक्ति *p प्रकार है Tतो, sizeof *pके रूप में ही परिणाम देता है sizeof (T), तो हम एक के लिए पर्याप्त जगह का आवंटन कर रहे हैं Nके तत्व सरणी T। यह किसी भी प्रकार केT लिए सही है ।

अब, Tएक सरणी प्रकार के साथ स्थानापन्न करते हैं R [10]। तब हमारा आवंटन हो जाता है

R (*p)[10] = malloc( sizeof *p * N);

यहां अर्थ विज्ञान हैं ठीक उसी -1 डी आवंटन पद्धति के रूप में; यह सब बदल दिया गया प्रकार है p। इसके बजाय T *, यह अब है R (*)[10]। अभिव्यक्ति *pका प्रकार है Tजो टाइप है R [10], इसलिए sizeof *pइसके बराबर है sizeof (T)जो इसके बराबर है sizeof (R [10])। इसलिए हम एक के लिए पर्याप्त जगह का आवंटन कर रहे हैं Nद्वारा 10की तत्व सरणी R

हम चाहे तो इसे और भी आगे ले जा सकते हैं; मान लीजिए कि Rएक सरणी प्रकार है int [5]। इसके लिए Rऔर हमें मिलता है

int (*p)[10][5] = malloc( sizeof *p * N);

एक ही सौदा - sizeof *pरूप में ही है sizeof (int [10][5]), और हम स्मृति बड़ा पर्याप्त का एक सन्निहित हिस्सा आवंटन एक धारण करने के लिए हवा Nसे 10द्वारा 5की सरणी int

तो वह आवंटन पक्ष है; पहुंच पक्ष के बारे में क्या?

याद रखें कि []सबस्क्रिप्ट ऑपरेशन सूचक अंकगणित के संदर्भ में परिभाषित किया गया है: 1 केa[i] रूप में परिभाषित किया गया है । इस प्रकार, सबस्क्रिप्ट ऑपरेटर को स्पष्ट रूप से एक पॉइंटर संदर्भित करता है। यदि कोई पॉइंटर है , तो आप इंगित किए गए मान तक पहुँच सकते हैं या तो स्पष्ट रूप से एकतरफा ऑपरेटर के साथ डीरेफरेंसिंग द्वारा :*(a + i)[] pT*

T x = *p;

या[] सबस्क्रिप्ट ऑपरेटर का उपयोग करके :

T x = p[0]; // identical to *p

इस प्रकार, यदि pकिसी सरणी के पहले तत्व को इंगित करता है , तो आप पॉइंटर पर एक सबस्क्रिप्ट का उपयोग करके उस सरणी के किसी भी तत्व तक पहुंच सकते हैं p:

T arr[N];
T *p = arr; // expression arr "decays" from type T [N] to T *
...
T x = p[i]; // access the i'th element of arr through pointer p

अब, हमारा प्रतिस्थापन ऑपरेशन फिर से करें और Tसरणी प्रकार के साथ बदलें R [10]:

R arr[N][10];
R (*p)[10] = arr; // expression arr "decays" from type R [N][10] to R (*)[10]
...
R x = (*p)[i];

एक तुरंत स्पष्ट अंतर; हम pसबस्क्रिप्ट ऑपरेटर को लागू करने से पहले स्पष्ट रूप से dereferencing हैं । हम इसमें सब्स्क्राइब नहीं करना चाहते हैं p, हम किस p बिंदु पर (इस मामले में, ऐरे arr[0] ) में सबस्क्रिप्ट करना चाहते हैं । चूंकि यूनिफ़ॉर्म *में सबस्क्रिप्ट []ऑपरेटर की तुलना में कम वरीयता होती है , इसलिए हमें स्पष्ट रूप से समूह में कोष्ठक का उपयोग pकरना होगा *। लेकिन ऊपर से याद है कि के *pरूप में ही है p[0], तो हम उस के साथ स्थानापन्न कर सकते हैं

R x = (p[0])[i];

या केवल

R x = p[0][i];

इस प्रकार, यदि p2 डी सरणी की ओर इशारा करते हैं, तो हम इस pतरह से उस सरणी में अनुक्रमित कर सकते हैं :

R x = p[i][j]; // access the i'th element of arr through pointer p;
               // each arr[i] is a 10-element array of R

इसे उसी निष्कर्ष पर ले जाना जैसा ऊपर और प्रतिस्थापन के Rसाथ है int [5]:

int arr[N][10][5];
int (*p)[10][5]; // expression arr "decays" from type int [N][5][10] to int (*)[10][5]
...
int x = p[i][j][k];

यह केवल एक ही काम करता है अगर pएक नियमित सरणी की ओर इशारा करता है, या अगर यह स्मृति के माध्यम से आवंटित करता है malloc

इस मुहावरे के निम्नलिखित लाभ हैं:

  1. यह आसान है - कोड की केवल एक पंक्ति, जैसा कि टुकड़े टुकड़े आवंटन विधि के विपरीत है
    T **arr = malloc( sizeof *arr * N );
    if ( arr )
    {
      for ( size_t i = 0; i < N; i++ )
      {
        arr[i] = malloc( sizeof *arr[i] * M );
      }
    }
  2. आवंटित सरणी की सभी पंक्तियाँ * सन्निहित * हैं, जो ऊपर दिए गए टुकड़े-टुकड़े आवंटन विधि के मामले में नहीं है;
  3. एक कॉल के साथ सरणी को डीलक्लोकेटिंग करना उतना ही आसान है free। फिर से, टुकड़े टुकड़े आवंटन विधि के साथ सच नहीं है, जहां आपको डील करने arr[i]से पहले प्रत्येक को निपटाना होगा arr

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


1. याद रखें कि सरणियाँ संकेत नहीं हैं - इसके बजाय, सरणी अभिव्यक्तियाँ आवश्यक रूप से सूचक अभिव्यक्तियों में बदल जाती हैं।


4
+1 मुझे वह तरीका पसंद है जो आप अवधारणा प्रस्तुत करते हैं: तत्वों की एक श्रृंखला आवंटित करना किसी भी प्रकार के लिए संभव है, भले ही उन तत्वों को खुद को गिरफ्तार किया गया हो।
logo_writer

1
आपकी व्याख्या वास्तव में अच्छी है, लेकिन ध्यान दें कि सन्निहित स्मृति का आवंटन एक ऐसा लाभ नहीं है जिसकी आपको वास्तव में आवश्यकता है। संक्रामक स्मृति अधिक महंगी है जो गैर-सन्निहित है। सरल 2 डी सरणियों के लिए आपके लिए मेमोरी लेआउट में कोई अंतर नहीं है (आवंटन और निपटान के लिए लाइनों की संख्या को छोड़कर), इसलिए गैर-सन्निहित मेमोरी का उपयोग करना पसंद करें।
बजे ओलेग लोकशिन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.