Çok boyutlu diziler bellekte nasıl biçimlendirilir?


185

C, dinamik olarak aşağıdaki kodu kullanarak yığın üzerinde iki boyutlu bir dizi ayırabilirsiniz biliyorum:

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

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

Açıkçası, bu aslında bir dizi ayrı tek boyutlu tamsayı dizisine tek boyutlu bir işaretçi dizisi oluşturur ve "Sistem" istediğimde ne demek istediğimi anlayabilir:

someNumbers[4][2];

Ama statik olarak aşağıdaki satırda olduğu gibi bir 2D dizi bildirdiğinizde ...:

int someNumbers[ARRAY_ROWS][ARRAY_COLUMNS];

... yığın üzerinde benzer bir yapı oluşturuluyor mu yoksa tamamen başka bir formda mı? (yani 1D işaretçiler dizisi mi? Değilse, nedir ve referanslar nasıl anlaşılır?)

Ayrıca, "Sistem" dediğimde, bunu anlamaktan aslında ne sorumlu? Çekirdek mi? Yoksa C derleyicisi derlerken bunu çözer mi?


8
Yapabilirsem +1'den fazla verirdim.
Rob Lachlan

1
Uyarı : Bu kodda 2B dizi yok!
Bu site için çok dürüst

@toohonestforthissite Gerçekten. Bunu genişletmek için: Döngü ve çağrı malloc()N-boyutlu bir diziye neden olmaz. . Tek boyutlu dizileri tamamen ayırmak için işaretçi dizileri [işaretçi dizileri [...]] ile sonuçlanır . Bkz doğru çok-boyutlu diziler tahsis ayırmaya görmek için DOĞRU , N-boyutlu bir dizi.
Andrew Henle

Yanıtlar:


145

Statik iki boyutlu bir dizi bir dizi diziye benzer - sadece bellekte bitişik olarak düzenlenir. Diziler işaretçilerle aynı şey değildir, ancak bunları çoğu zaman birbirlerinin yerine kullanabileceğiniz için bazen kafa karıştırıcı olabilir. Derleyici, her şeyi güzel bir şekilde hizalayan düzgün bir şekilde izler. Bahsettiğiniz gibi statik 2D dizilere dikkat etmelisiniz, çünkü bir int **parametreyi alan bir işleve geçmeye çalışırsanız , kötü şeyler olur. İşte kısa bir örnek:

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

Bellekte şöyle görünür:

0 1 2 3 4 5

tam olarak aynı:

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

Ancak array1bu işleve geçmeye çalışırsanız :

void function1(int **a);

bir uyarı alırsınız (ve uygulama diziye doğru bir şekilde erişemez):

warning: passing argument 1 of function1 from incompatible pointer type

Çünkü bir 2D dizi ile aynı değildir int **. Bir dizinin bir işaretçiye otomatik olarak çürümesi tabiri caizse "bir seviye derine" gider. İşlevi şu şekilde bildirmeniz gerekir:

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

veya

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

Her şeyi mutlu etmek için.

Aynı kavram n boyutlu dizilere de uzanır . Uygulamanızda bu tür komik işlerden yararlanmak, genellikle sadece anlaşılmasını zorlaştırır. O yüzden dikkatli ol.


Açıklama için teşekkürler. Yani "void function2 (int a [] [2]);" statik ve dinamik olarak beyan edilmiş 2D'leri kabul eder mi? Ve sanırım ilk boyut [] olarak bırakılırsa dizinin uzunluğunu geçmek hala iyi bir uygulama / esastır?
Chris Cooper

1
@Chris Ben öyle düşünmüyorum - C swizzle'ı bir yığın işaretçi veya küresel olarak ayrılmış bir dizi işaretçi haline getirmekte zorlanacaksınız.
Carl Norum

6
@JasonK. - Hayır. Diziler işaretçi değildir. Diziler bazı bağlamlarda işaretçilere "bozulur", ancak bunlar kesinlikle aynı değildir .
Carl Norum

1
Açık olmak gerekirse: Evet Chris "dizinin uzunluğunu geçmek ayrı bir parametre olarak hala iyi bir uygulamadır", aksi takdirde std :: array veya std :: vector (C ++ eski C değil) kullanın. @CarlNorum'a hem yeni kullanıcılar için hem de pratik olarak Quora'da Anders Kaseorg'u alıntılamak konusunda hemfikir olduğumuzu düşünüyorum: “C öğrenmenin ilk adımı, işaretçilerin ve dizilerin aynı şey olduğunu anlamaktır. İkinci adım, işaretçilerin ve dizilerin farklı olduğunu anlamaktır. ”
Jason K.15

2
@JasonK. "C öğrenmenin ilk adımı, işaretçilerin ve dizilerin aynı şey olduğunu anlamaktır." - Bu teklif çok yanlış ve yanıltıcı! Olduklarını anlamak gerçekten en önemli adımdır değil aynı, ama bu diziler vardır dönüştürülen için bir işaretçi ilk elemanı en operatörler için! sizeof(int[100]) != sizeof(int *)( 100 * sizeof(int)bayt / ile bir platform bulamazsanız int, ancak bu farklı bir şeydir.
Bu site için çok dürüst

85

Cevap C gerçekten etmediğini fikrine dayanmaktadır var 2D diziler - bu diziler-of-the diziler vardır. Bunu beyan ettiğinizde:

int someNumbers[4][2];

someNumbers4 öğeden oluşan bir dizi olmasını istiyorsunuz , bu dizinin her bir öğesi tür int [2](kendisinin 2 ints dizisidir ).

Bulmacanın diğer kısmı dizilerin her zaman bitişik olarak belleğe yerleştirilmesidir. İsterseniz:

sometype_t array[4];

o zaman bu her zaman şöyle görünecektir:

| sometype_t | sometype_t | sometype_t | sometype_t |

( sometype_tAralarında boşluk bırakmadan yan yana yerleştirilmiş 4 nesne). Senin Yani someNumbersdizi-of-the diziler, böyle bakacağız:

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

Ve her int [2]öğenin kendisi bir dizidir, şöyle görünür:

| int        | int        |

Genel olarak, bunu elde edersiniz:

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

1
son düzene baktığımda int [] [] 'ye int * olarak erişilebileceğini düşündürüyor değil mi?
Narcisse Doudieu Siewe

2
@ user3238855: Türler uyumlu değil, ancak intdiziler dizisinde birinciye bir işaretçi alırsanız (örn. a[0]veya değerlendirerek &a[0][0]), evet, her birine sırayla erişmek için bunu dengeleyebilirsiniz int.
caf

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

bellekte eşittir:

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

5

Ayrıca sizin yanıtınıza: Her ikisi de, derleyici ağır kaldırma çoğunu yapıyor olsa da.

Statik olarak tahsis edilen diziler durumunda, derleyici "Sistem" olacaktır. Belleği herhangi bir yığın değişkeni için ayırdığı gibi ayıracaktır.

Malloc'd dizisi durumunda, "Sistem" malloc (genellikle çekirdek) uygulayıcısı olacaktır. Derleyicinin ayıracağı tüm temel işaretçi.

Derleyici, Carl'ın değiştirilebilir kullanımı anlayabileceği yerlerde verdiği örnek haricinde, türü her zaman olduğu gibi işleyecektir. Bu nedenle, bir işleve [] [] iletirseniz, işlevinin statik olarak ayrılmış bir düz olduğunu varsayması gerekir; burada **, işaretçiden işaretçiye işaretçi olduğu varsayılır.


Maljon'un çekirdek tarafından uygulandığını söyleyemem, ancak çekirdek ilkellerinin üstündeki libc tarafından (brk gibi)
Manuel Selva

@ManuelSelva: Nerede ve nasıl mallocuygulanır standart tarafından belirtilmez ve uygulamaya bırakılır, sırasıyla. ortamı. Bağımsız ortamlar için, standart kütüphanenin bağlantı fonksiyonları gerektiren tüm parçaları gibi isteğe bağlıdır (standart durumların edebi olarak değil, gereksinimlerin gerçekte sonuçlandığı budur). Bazı modern barındırılan ortamlar için, tam işlevler veya (örn. Linux) çekirdek işlevlerine, stdlib ve çekirdek ilkellerini kullanarak yazdığınız gibi. Sanal olmayan bellek tek işlem sistemleri için yalnızca stdlib olabilir.
Bu site için çok dürüst

2

Elimizdeki, varsayalım a1ve a2aşağıdaki (C99) gibi tanımlanmıştır ve başlatıldı:

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

a1bellekte düz sürekli yerleşime sahip homojen bir 2D dizidir ve ifade (int*)a1ilk öğesinin göstergesine göre değerlendirilir:

a1 --> 142 143 144 145

a2heterojen bir 2D diziden başlatılır ve bir tür değerine bir göstericidir int*, yani dereference ifadesi *a2türün bir değeri olarak değerlendirilir int*, bellek düzeninin sürekli olması gerekmez:

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

Tamamen farklı bellek düzeni ve erişim semantiğine rağmen, dizi erişimi ifadeleri için C dili dilbilgisi hem homojen hem de heterojen 2D dizi için tamamen aynı görünüyor:

  • ifadesi a1[1][0]değeri dizi 144dışına getireceka1
  • ifadesi a2[1][0]değeri dizi 244dışına getireceka2

Derleyici için erişim ifade bilir a1tipine faaliyet int[2][2]için erişim ifade zaman, a2türüne çalışır int**. Oluşturulan montaj kodu, homojen veya heterojen erişim semantiğini izleyecektir.

Kod, tür dizisi yayınlandığında int[N][M]ve daha sonra tür olarak erişildiğinde genellikle çalışma zamanında kilitlenir, int**örneğin:

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

1

Belirli bir 2D diziye erişmek için, aşağıdaki kodda gösterildiği gibi bir dizi bildirimi için bellek haritasını göz önünde bulundurun:

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

Her bir öğeye erişmek için, işleve parametre olarak ilgilendiğiniz diziyi geçmek yeterlidir. Ardından her bir öğeye ayrı ayrı erişmek için sütun için ofseti kullanın.

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;
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.