स्मृति में बहु-आयामी सरणियों को कैसे स्वरूपित किया जाता है?


184

सी में, मुझे पता है कि मैं गतिशील रूप से निम्नलिखित कोड का उपयोग करके ढेर पर एक दो-आयामी सरणी आवंटित कर सकता हूं:

int** someNumbers = malloc(arrayRows*sizeof(int*));

for (i = 0; i < arrayRows; i++) {
    someNumbers[i] = malloc(arrayColumns*sizeof(int));
}

स्पष्ट रूप से, यह वास्तव में पूर्णांक के अलग-अलग आयामी आयामी सरणियों के एक समूह के लिए एक आयामी आयाम बनाता है, और "सिस्टम" यह पता लगा सकता है कि जब मैं पूछता हूं तो मेरा क्या मतलब है:

someNumbers[4][2];

लेकिन जब मैं सांख्यिकीय रूप से एक 2 डी सरणी की घोषणा करता हूं, जैसा कि निम्नलिखित पंक्ति में है: ...

int someNumbers[ARRAY_ROWS][ARRAY_COLUMNS];

... क्या एक समान संरचना स्टैक पर बनाई गई है, या यह पूरी तरह से एक और रूप है? (यानी यह बिंदुओं का 1D सरणी है? यदि नहीं, तो यह क्या है, और इसके संदर्भ कैसे प्राप्त होते हैं?)

इसके अलावा, जब मैंने कहा, "द सिस्टम," वास्तव में यह पता लगाने के लिए क्या जिम्मेदार है? गिरी? या संकलन करते समय C संकलक इसे क्रमबद्ध करता है?


8
अगर मैं कर सकता था तो मैं +1 से अधिक दूंगा।
रोब लछलन

1
चेतावनी : इस कोड में कोई 2D सरणी नहीं है!
इस साइट के लिए बहुत ही ईमानदार है

@toohonestforthissite वास्तव में। उस पर विस्तार करने के लिए: लूपिंग और कॉलिंग malloc()में एन-आयामी सरणी नहीं होती है। । यह बिंदुओं के सरणियों [बिंदुओं के सरणियों [...]] को पूरी तरह से एक आयामी सरणियों को अलग करने के परिणामस्वरूप होता है । TRUE N- आयामी सरणी को कैसे आवंटित किया जाए, यह देखने के लिए बहु-आयामी सरणियों को सही ढंग से आवंटित करें देखें ।
एंड्रयू हेनले

जवाबों:


144

एक स्थिर दो आयामी सरणी सरणियों के एक सरणी की तरह लग रहा है - यह सिर्फ स्मृति में आकस्मिक रूप से निर्धारित किया गया है। Arrays संकेत के रूप में एक ही बात नहीं कर रहे हैं, लेकिन क्योंकि आप अक्सर उन्हें बहुत ज्यादा परस्पर उपयोग कर सकते हैं यह कभी-कभी भ्रमित हो सकता है। कंपाइलर ठीक से ट्रैक करता है, हालांकि, जो सब कुछ अच्छी तरह से लाइन करता है। आपके द्वारा उल्लेख किए गए स्थिर 2D सरणियों से आपको सावधान रहना होगा, क्योंकि यदि आप किसी int **पैरामीटर को लेने वाले फ़ंक्शन को पास करने की कोशिश करते हैं , तो खराब चीजें होने वाली हैं। यहाँ एक त्वरित उदाहरण है:

int array1[3][2] = {{0, 1}, {2, 3}, {4, 5}};

स्मृति में इस तरह दिखता है:

0 1 2 3 4 5

बिल्कुल वैसा ही:

int array2[6] = { 0, 1, 2, 3, 4, 5 };

लेकिन अगर आप array1इस समारोह में जाने की कोशिश करते हैं:

void function1(int **a);

आपको एक चेतावनी मिलेगी (और ऐप सही तरीके से सरणी तक पहुंचने में विफल रहेगा):

warning: passing argument 1 of function1 from incompatible pointer type

क्योंकि एक 2D सरणी के रूप में ही नहीं है int **। एक पॉइंटर में एक सरणी का स्वत: क्षय केवल "एक स्तर गहरा" होता है इसलिए बोलने के लिए। आपको फ़ंक्शन को इस रूप में घोषित करने की आवश्यकता है:

void function2(int a[][2]);

या

void function2(int a[3][2]);

सब कुछ खुश करने के लिए।

यह समान अवधारणा एन -डायमेंशनल सरणियों तक फैली हुई है । अपने आवेदन में इस तरह के मज़ेदार व्यवसाय का लाभ उठाते हुए आम तौर पर इसे समझना मुश्किल हो जाता है, हालांकि। इसलिए वहां से सावधान रहें।


स्पष्टीकरण के लिए धन्यवाद। तो "शून्य फ़ंक्शन 2 (इंट [ए] [2]);" दोनों को सांख्यिकीय और गतिशील रूप से 2D घोषित किया जाएगा? और मुझे लगता है कि सरणी की लंबाई में पास होने के लिए यह अभी भी अच्छा अभ्यास / आवश्यक है यदि पहले आयाम को छोड़ दिया जाए []?
क्रिस कूपर

1
@ क्रिस ऐसा नहीं लगता है - आपके पास एक कठिन समय होगा कि आप सी-स्विज़ल को स्टैक या ग्लोबली-आवंटित सरणी को पॉइंटर्स के झुंड में बना सकें।
कार्ल नॉरम

6
@JasonK। - नहीं। ऐरे पॉइंटर्स नहीं हैं। कुछ संदर्भों में संकेत में "क्षय" को रोकता है, लेकिन वे बिल्कुल समान नहीं हैं ।
कार्ल नॉरम

1
स्पष्ट होने के लिए: हाँ क्रिस "एक अलग पैरामीटर के रूप में" सरणी की लंबाई में पास होना अभी भी अच्छा अभ्यास है, अन्यथा std :: array या std :: वेक्टर (जो C ++ पुराना नहीं है C) का उपयोग करें। मुझे लगता है कि हम नए उपयोगकर्ताओं के लिए वैचारिक रूप से @CarlNorum से सहमत हैं और व्यावहारिक रूप से, Quora पर एंडर्स केसेर्ग को उद्धृत करने के लिए: “C सीखने का पहला चरण यह समझ रहा है कि संकेत और सरणियाँ एक ही बात हैं। दूसरा चरण यह समझ रहा है कि संकेत और सरणियाँ अलग हैं। ”
जेसन के।

2
@JasonK। "C सीखने का पहला चरण यह समझ रहा है कि संकेत और सरणियाँ एक ही चीज़ हैं।" - यह उद्धरण बहुत गलत और भ्रामक है! यह समझना वास्तव में सबसे महत्वपूर्ण कदम है कि वे समान नहीं हैं , लेकिन यह है कि सरणियों को अधिकांश ऑपरेटरों के लिए पहले तत्व के लिए एक सूचक में बदल दिया जाता है ! (जब तक आपको बाइट्स / के साथ एक प्लेटफ़ॉर्म नहीं मिलता है , लेकिन यह एक अलग बात है।sizeof(int[100]) != sizeof(int *)100 * sizeof(int)int
इस साइट के लिए बहुत ही ईमानदार

84

इसका उत्तर इस विचार पर आधारित है कि C में वास्तव में 2D सरणियाँ नहीं हैं - इसमें सरणियाँ-सरणियाँ हैं। जब आप यह घोषणा करते हैं:

int someNumbers[4][2];

आप someNumbers4 तत्वों की एक सरणी होने के लिए कह रहे हैं , जहां उस सरणी का प्रत्येक तत्व प्रकार का है int [2](जो स्वयं 2 ints की एक सरणी है )।

पहेली का दूसरा हिस्सा यह है कि सरणियों को हमेशा स्मृति में आकस्मिक रूप से रखा जाता है। यदि आप पूछते हैं:

sometype_t array[4];

तब वह हमेशा इस तरह दिखेगा:

| sometype_t | sometype_t | sometype_t | sometype_t |

(4 sometype_tवस्तुओं को एक दूसरे के बगल में रखा गया है, जिनके बीच में कोई स्थान नहीं है)। तो आपके someNumbersसरणी में , यह इस तरह दिखेगा:

| int [2]    | int [2]    | int [2]    | int [2]    |

और प्रत्येक int [2]तत्व स्वयं एक सरणी है, जो इस तरह दिखता है:

| int        | int        |

तो कुल मिलाकर, आप इसे प्राप्त करते हैं:

| int | int  | int | int  | int | int  | int | int  |

1
अंतिम लेआउट को देखकर मुझे लगता है कि int [] [] int * के रूप में पहुँचा जा सकता है ... सही है?
Narcisse Doudieu Siewe

2
@ user3238855: प्रकार संगत नहीं हैं, लेकिन यदि आपको intसरणियों के सरणी में पहली बार एक सूचक मिलता है (उदाहरण के लिए मूल्यांकन करके a[0]या &a[0][0]तो), तो, आप इसे क्रमिक रूप से हर तक पहुंचने के लिए ऑफसेट कर सकते हैं int)।
कैफे

28
unsigned char MultiArray[5][2]={{0,1},{2,3},{4,5},{6,7},{8,9}};

स्मृति में बराबर है:

unsigned char SingleArray[10]={0,1,2,3,4,5,6,7,8,9};

5

आपके उत्तर के लिए भी: दोनों, हालांकि संकलक अधिकांश भारी उठाने का काम कर रहा है।

सांख्यिकीय रूप से आवंटित सरणियों के मामले में, "द सिस्टम" कंपाइलर होगा। यह मेमोरी को आरक्षित करेगा जैसे यह किसी भी स्टैक चर के लिए होगा।

Malloc'd सरणी के मामले में, "सिस्टम" Malloc का कार्यान्वयनकर्ता होगा (कर्नेल आमतौर पर)। सभी संकलक आवंटित करेगा आधार सूचक है।

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


@ जोंन एल। मैं यह नहीं कहूंगा कि कर्नेल द्वारा मॉलोक को लागू किया जाता है, लेकिन कर्नेल प्राइमेटिक्स (जैसे brk) के ऊपर परिवाद द्वारा
मैनुअल सेल्वा

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

2

मान लीजिए, हमने नीचे (c99) की तरह परिभाषित a1और a2परिभाषित किया है:

int a1[2][2] = {{142,143}, {144,145}};
int **a2 = (int* []){ (int []){242,243}, (int []){244,245} };

a1स्मृति और अभिव्यक्ति में सादे निरंतर लेआउट के साथ एक सजातीय 2 डी सरणी है जिसका (int*)a1संकेत इसके पहले तत्व को दिया जाता है:

a1 --> 142 143 144 145

a2एक विषम 2D सरणी से आरंभीकृत किया गया है और एक प्रकार के मान के लिए एक संकेतक है int*, अर्थात डीरेफेरेंस एक्सप्रेशन *a2प्रकार के मान में मूल्यांकन int*करता है, मेमोरी लेआउट को निरंतर नहीं होना चाहिए:

a2 --> p1 p2
       ...
p1 --> 242 243
       ...
p2 --> 244 245

पूरी तरह से अलग मेमोरी लेआउट और एक्सेस शब्दार्थ के बावजूद, सरणी-पहुंच अभिव्यक्तियों के लिए सी-भाषा व्याकरण दोनों सजातीय और विषम 2 डी सरणी के लिए बिल्कुल समान दिखता है:

  • अभिव्यक्ति सरणी से बाहर a1[1][0]मूल्य प्राप्त करेंगे144a1
  • अभिव्यक्ति सरणी से बाहर a2[1][0]मूल्य प्राप्त करेंगे244a2

कंपाइलर जानता है कि एक्सेस-एक्सप्रेशन a1टाइप पर संचालित होता है int[2][2], जब एक्सेस-एक्सप्रेशन a2टाइप पर ऑपरेट होता है int**। उत्पन्न विधानसभा कोड सजातीय या विषम पहुंच शब्दार्थ का पालन करेगा।

कोड आमतौर पर रन-टाइम पर दुर्घटनाग्रस्त हो जाता है जब प्रकार की सरणी टाइप की int[N][M]जाती है और फिर int**उदाहरण के लिए टाइप की जाती है :

((int**)a1)[1][0]   //crash on dereference of a value of type 'int'

1

किसी विशेष 2D सरणी तक पहुँचने के लिए किसी सरणी घोषणा के लिए स्मृति मानचित्र पर विचार करें जैसा कि नीचे दिए गए कोड में दिखाया गया है:

    0  1
a[0]0  1
a[1]2  3

प्रत्येक तत्व को एक्सेस करने के लिए, इसकी पर्याप्त बस पास करने के लिए जो आप फ़ंक्शन के मापदंडों के रूप में रुचि रखते हैं। फिर प्रत्येक तत्व को व्यक्तिगत रूप से एक्सेस करने के लिए कॉलम के लिए ऑफ़सेट का उपयोग करें।

int a[2][2] ={{0,1},{2,3}};

void f1(int *ptr);

void f1(int *ptr)
{
    int a=0;
    int b=0;
    a=ptr[0];
    b=ptr[1];
    printf("%d\n",a);
    printf("%d\n",b);
}

int main()
{
   f1(a[0]);
   f1(a[1]);
    return 0;
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.