İki boyutlu diziyi ayırmanın garip bir yolu mu?


110

Bir projede, birisi şu çizgiyi itti:

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

İki boyutlu bir (n + 1) * (n + 1) dizisi oluşturduğu varsayılır.

Sözüm ona, diyorum, çünkü şu ana kadar sorduğum hiç kimse bana bunun tam olarak ne yaptığını, nereden kaynaklandığını ya da neden işe yaradığını söyleyemedi (iddiaya göre öyle ama henüz satın almıyorum).

Belki bariz bir şeyi kaçırıyorum, ama biri bana yukarıdaki satırı açıklayabilirse çok sevinirim. Çünkü kişisel olarak, gerçekten anladığımız bir şeyi kullanırsak çok daha iyi hissederim.


15
Çalışsa bile, iticiyi ateşleyin.
Martin James

22
@MartinJames Neden? Aksi halde bitişik belleğe bir 2D diziyi nasıl ayırırsınız? Karışık bir 1D dizisi mi? Bu 1990'ların programlama, artık VLA'larımız var.
Lundin

43
Kayıt için, o ise bir ve dinamik bir gerçek 2D dizi tahsis etmenin tek yolu.
Quentin

15
@Kninnug Hayır, bu bir 2D dizi değil, bir işaretçi dizisi bildirir. 2B bir dizi istiyorsanız, bir dizi işaretçi ayırmak istemeniz için hiçbir neden yoktur. Yığın parçalanması ve zayıf önbellek kullanımı nedeniyle yavaştır ve güvensizdir çünkü onu bir dizi olarak kullanamazsınız (memcpy vb. İle). Artı kod şişirilmiş. Birden çok free () çağrıya ihtiyacınız olacak ve bellek sızıntılarına neden olmak kolay olacaktır. Böyle bir kod ne kadar yaygın olursa olsun, düpedüz kötüdür.
Lundin

15
Örnek aynı değerli boyutları kullanmasaydı, bu problemi açıklamak / cevaplamak daha net olurdu, n+1bunun yerinedouble (*e)[rows] = malloc(columns * sizeof *e);
chux - Yeniden eski haline getir Monica

Yanıtlar:


87

Değişken e, n + 1türdeki öğeler dizisine bir göstericidir double.

Üzerinde dereference operatörünü kullanmak esize temel türü " türdeki öğeler edizisi " olan verir.n + 1double

mallocÇağrı sadece baz tipi alır e(yukarıda açıklanmıştır) ve göre, çarpma büyüklüğü alır n + 1, ve bu boyutu geçen mallocfonksiyonu. Esasen bir dizi ayırma n + 1dizileri n + 1unsurları double.


3
@MartinJames sizeof(*e)eşdeğerdir sizeof(double [n + 1]). Bunu ile çarpın n + 1ve yeterince alırsınız.
Bir programcı dostum

24
@MartinJames: Bunun nesi var? Bu o kadar da göze batmayan bir şey değil, tahsis edilen satırların bitişik olmasını garanti ediyor ve diğer 2D diziler gibi dizine ekleyebiliyorsunuz. Bu deyimi kendi kodumda çok kullanıyorum.
John Bode

3
Açık görünebilir, ancak bu yalnızca kare diziler için işe yarar (aynı boyutlar).
Jens

18
@Jens: Sadece koyarsan n+1 her iki boyut için , sonucun kare olacağı . Bunu yaparsanız double (*e)[cols] = malloc(rows * sizeof(*e));, sonuçta belirttiğiniz sayıda satır ve sütun olacaktır.
user2357112 Monica'yı

9
@ user2357112 Şimdi görmeyi çok isterim. int rows = n+1Ve eklemeniz gerektiği anlamına gelse bile int cols = n+1. Tanrı bizi akıllı koddan korusun.
şeker_turuncu

56

Bu, 2B dizileri dinamik olarak ayırmanız gereken tipik yoldur.

  • ebir dizi türü için bir dizi göstericisidir double [n+1].
  • sizeof(*e)bu nedenle, tek bir double [n+1]dizinin boyutu olan sivri uçlu türün türünü verir .
  • Bu n+1tür diziler için yer ayırırsınız.
  • Dizi işaretçisini ebu diziler dizisindeki ilk diziyi gösterecek şekilde ayarlarsınız .
  • Bu kullanmanızı sağlar eolarak e[i][j]2D dizide erişim bireysel öğelere.

Şahsen bu tarzın okunmasının çok daha kolay olduğunu düşünüyorum:

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

12
Güzel cevap, ancak önerdiğiniz stile katılmıyorum, stili tercih ediyorum ptr = malloc(sizeof *ptr * count).
chux

Güzel cevap ve tercih ettiğiniz stili beğendim. Küçük bir gelişme, bu şekilde yapmanız gerektiğini belirtmek olabilir çünkü dikkate alınması gereken satırlar arasında dolgu olabilir. (En azından, bunu bu şekilde yapmanın nedeni bu bence.) (Yanılıyorsam bana haber ver.)
davidbak

2
@davidbak Bu aynı şey. Dizi sözdizimi yalnızca kendi kendini belgeleyen bir koddur: kaynak kodun kendisiyle birlikte "2D dizi için yer ayır" der.
Lundin

1
@davidbak Not: Taşma olduğunda yorumun küçük bir dezavantajı malloc(row*col*sizeof(double))ortaya çıkar row*col*sizeof(), ancak sizeof()*row*cololuşmaz. (örneğin satır, sütunlar int)
chux - Monica'yı yeniden etkinleştir

7
@davidbak: sizeof *e * (n+1)bakımı daha kolaydır; Temel türü değiştirmeye karar verirseniz ( örneğin ' den' doublee long double), o zaman yalnızca bildirimini değiştirmeniz gerekir e; aramadaki sizeofifadeyi değiştirmenize gerek yoktur malloc(bu, zamandan tasarruf etmenizi sağlar ve sizi bir yerde değiştirmekten korur, ancak diğerinde değiştirmez). sizeof *eher zaman size doğru bedeni verecektir.
John Bode

39

Bu deyim doğal olarak 1B dizi tahsisinin dışında kalır. Bazı rasgele türden bir 1B dizisi ayırarak başlayalım T:

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

Basit, değil mi? İfadesi *p türü vardır T, böylece, sizeof *paynı sonucu verir sizeof (T)bu yüzden bir için yeterli alan tahsis ediyoruz, Nbir -eleman dizisi T. Bu her türT için geçerlidir .

Şimdi, Tgibi bir dizi türü ile değiştirelim R [10]. Daha sonra tahsisatımız

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

Buradaki anlambilim 1D tahsis yöntemiyle tamamen aynıdır ; tüm değişen türü p. Bunun yerine T *şimdi R (*)[10]. İfade *p, type Tolan türe sahiptir R [10], bu sizeof *pnedenle eşdeğer sizeof (T)olan ile eşdeğerdir sizeof (R [10]). Dolayısıyla N, 10öğeye göre bir dizi için yeterli alan ayırıyoruz:R .

İstersek bunu daha da ileri götürebiliriz; varsayalım Rkendisi bir dizi türüdür int [5]. Bunun yerine koy Rve biz

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

Aynı anlaşma - sizeof *paynıdır sizeof (int [10][5])ve biz tutmak için bellek yeterince büyük bitişik bir yığın tahsis kadar rüzgar Ntarafından 10tarafından 5dizisiint .

Yani bu tahsis tarafı; erişim tarafı ne olacak?

Alt []simge işleminin işaretçi aritmetiği açısından tanımlandığını unutmayın : 1a[i] olarak tanımlanır . Böylece, alt simge operatörü dolaylı olarak bir göstericiye referansta bulunur. Eğer bir göstericidir açıkça tekli ile kaldırma tarafından sivri-değerin ya erişebileceğiniz operatörü:*(a + i)[] pT*

T x = *p;

veya[] alt simge operatörünü kullanarak :

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

Böylece, pbir dizinin ilk öğesini işaret ediyorsa , işaretçi üzerinde bir alt simge kullanarak bu dizinin herhangi bir öğesine erişebilirsiniz 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

Şimdi ikame işlemimizi tekrar yapalım Tve dizi tipiyle değiştirelim 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];

Hemen görünen bir fark; palt simge operatörünü uygulamadan önce açıkça başvuruyu kaldırıyoruz . Alt simge yapmak istemiyoruz p, neyin p işaret ettiğini (bu durumda dizi arr[0] ) belirtmek istiyoruz . Tekli yana *simge daha düşük önceliğe sahiptir []operatörü, biz açıkça gruba parantez kullanmak zorunda polan *. Ancak yukarıdan bunun *paynısı olduğunu unutmayın p[0], böylece onu yerine koyabiliriz

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

ya da sadece

R x = p[0][i];

Böylece, p2B bir diziyi gösteriyorsa, bu diziyi şu şekilde indeksleyebiliriz 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

Bunu yukarıdaki ile aynı sonuca götürmek ve aşağıdakilerle ikame Retmek 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];

Bu işler sadece aynı ise pnoktalar düzenli diziye yoksa belleğe işaret eğer yoluyla ayrılanmalloc .

Bu deyimin aşağıdaki faydaları vardır:

  1. Basit - parça parça ayırma yönteminin aksine yalnızca bir satır kod
    T **arr = malloc( sizeof *arr * N );
    if ( arr )
    {
      for ( size_t i = 0; i < N; i++ )
      {
        arr[i] = malloc( sizeof *arr[i] * M );
      }
    }
  2. Tahsis edilen dizinin tüm satırları * bitişiktir *, bu yukarıdaki parçalı ayırma yönteminde geçerli değildir;
  3. Dizinin serbest bırakılması, tek bir çağrı ile aynı derecede kolaydır free. Yine, arr[i]ayrılmadan önce her birini serbest bırakmanız gereken parçalı tahsis yöntemi için doğru değildir arr.

Bazen parçalı ayırma yöntemi tercih edilir, örneğin yığınınız kötü bir şekilde parçalanmışsa ve belleğinizi bitişik bir yığın olarak ayıramazsanız veya her satırın farklı bir uzunluğa sahip olabileceği "pürüzlü" bir dizi ayırmak istediğinizde. Ancak genel olarak, gitmenin daha iyi yolu budur.


1. Dizilerin işaretçi olmadığını unutmayın - bunun yerine, dizi ifadeleri gerektiği gibi işaretçi ifadelerine dönüştürülür.


4
+1 Kavramı sunma şeklinizi beğendim: bir dizi öğe ayırmak, bu öğeler dizinin kendisi olsa bile her tür için mümkündür.
logo_writer

1
Açıklamanız gerçekten iyidir, ancak bitişik belleğin tahsisinin gerçekten ihtiyacınız olana kadar bir fayda olmadığını unutmayın. Bitişik bellek, bitişik olmayan belleğe göre daha pahalıdır. Basit 2D diziler için bellek düzeninde sizin için bir fark yoktur (ayırma ve ayırma için satır sayısı dışında), bu nedenle bitişik olmayan bellek kullanmayı tercih edin.
Oleg Lokshyn
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.