İki boyutlu diziye bir işaretçi oluşturun


120

Statik bir 2 boyutlu diziye bir göstericiye ihtiyacım var. Bu nasıl yapılır?

static uint8_t l_matrix[10][20];

void test(){
   uint8_t **matrix_ptr = l_matrix; //wrong idea 
}

Gibi her türlü hatayı alıyorum:

  • uyarı: uyumsuz işaretçi türünden atama
  • indisli değer ne dizi ne de işaretçi
  • hata: geçersiz esnek dizi üyesi kullanımı


1
@ JohannesSchaub-litb Artık yok. (? Tekrar nasıl görür ... Ben biliyorum ... düşük rep üyeleri görüntüleyebilir, ama nasıl ben unuttum)
Mateen Ulhaq

1
@muntoo: İşte bir kopyası: gist.github.com/sharth/ede13c0502d5dd8d45bd
Bill Lynch

Yanıtlar:


140

Burada dizinin ilk elemanına bir işaretçi yapmak istiyorsunuz

uint8_t (*matrix_ptr)[20] = l_matrix;

Typedef ile bu daha temiz görünüyor

typedef uint8_t array_of_20_uint8_t[20];
array_of_20_uint8_t *matrix_ptr = l_matrix;

O zaman yeniden hayatın tadını çıkarabilirsin :)

matrix_ptr[0][1] = ...;

C'deki işaretçi / dizi dünyasına dikkat edin, bu konuda çok fazla kafa karışıklığı var.


Düzenle

Buradaki diğer cevaplardan bazılarını gözden geçiriyorum, çünkü yorum alanları orada yapmak için çok kısa. Birden çok alternatif önerildi, ancak nasıl davrandıkları gösterilmedi. İşte nasıl yaptıkları

uint8_t (*matrix_ptr)[][20] = l_matrix;

Hatayı düzeltirseniz &ve aşağıdaki kod parçacığındaki gibi operatörün adresini eklerseniz

uint8_t (*matrix_ptr)[][20] = &l_matrix;

Daha sonra bu, 20 uint8_t dizisinin tamamlanmamış dizi türündeki öğelere bir işaretçi yaratır. İşaretçi bir dizi dizisi için olduğundan, ona erişmeniz gerekir.

(*matrix_ptr)[0][1] = ...;

Bu eksik bir dizi için bir işaretçi çünkü, sen olamaz kısayol olarak yapmak

matrix_ptr[0][0][1] = ...;

İndeksleme, öğe türünün boyutunun bilinmesini gerektirdiğinden (indeksleme, işaretçiye bir tamsayı eklenmesi anlamına gelir, bu nedenle tamamlanmamış türlerle çalışmaz). Bu sadece çalıştığını Not Cçünkü T[]ve T[N]uyumlu tiplerdir. C ++ 'nın uyumlu türler kavramı yoktur ve bu nedenle bu kodu reddedecektir, çünkü T[]ve T[10]farklı türlerdir.


Aşağıdaki alternatif hiç çalışmıyor çünkü diziyi tek boyutlu bir dizi olarak görüntülediğinizde öğe türü değil uint8_t ,uint8_t[20]

uint8_t *matrix_ptr = l_matrix; // fail

Aşağıdaki iyi bir alternatiftir

uint8_t (*matrix_ptr)[10][20] = &l_matrix;

Şununla erişiyorsun

(*matrix_ptr)[0][1] = ...;
matrix_ptr[0][0][1] = ...; // also possible now

Dış boyutun büyüklüğünü korumasının yararı vardır. Böylece üzerine sizeof uygulayabilirsiniz

sizeof (*matrix_ptr) == sizeof(uint8_t) * 10 * 20

Bir dizideki öğelerin bitişik olarak depolandığı gerçeğinden yararlanan başka bir yanıt daha var.

uint8_t *matrix_ptr = l_matrix[0];

Şimdi, bu resmi olarak yalnızca iki boyutlu dizinin ilk öğesinin öğelerine erişmenize izin verir. Yani, aşağıdaki koşul geçerli

matrix_ptr[0] = ...; // valid
matrix_ptr[19] = ...; // valid

matrix_ptr[20] = ...; // undefined behavior
matrix_ptr[10*20-1] = ...; // undefined behavior

Muhtemelen işe yaradığını fark edeceksiniz 10*20-1, ancak takma ad analizi ve diğer agresif optimizasyonları kullanırsanız, bazı derleyiciler bu kodu bozabilecek bir varsayımda bulunabilir. Bunu söyledikten sonra, başarısız olan bir derleyiciyle hiç karşılaşmadım (ama yine de, bu tekniği gerçek kodda kullanmadım) ve C SSS bile bu tekniği içeriyor (UB'ness hakkında bir uyarı ile) ) ve dizi türünü değiştiremezseniz, bu sizi kurtarmak için son seçenektir :)


+1 - int (*) [] [20] dökümü hakkında güzel bilgi - bunu C ++ 'da yapamazsın
Faisal Vali

@litb, özür dilerim ama bu yanlış çünkü çözümünüz dizi için herhangi bir depolama ayırması sağlamıyor.
Rob Wells

2
@Rob, seni tam olarak anlamıyorum. tüm bu durumlarda depolama l_matix dizisinin kendisi tarafından sağlanır. İşaretçiler, içinde ve olarak bildirildikleri yerden (yığın, statik veri segmenti, ...) depolama alırlar.
Johannes Schaub - litb

Sadece merak ediyorum neden l_matrix'in "&" adresine ihtiyacımız var?
elektro

1
@Sohaib - hayır, bu yalnızca bir işaretçi oluşturur. uint8_t *d[20]Uint8_t'ye 3 işaretçi dizisi oluşturan ile karıştırmış olabilirsiniz , ancak bu durumda bu işe yaramaz.
Palo

28

İçin tam Bunu anlamak, sen gerekir aşağıdaki kavramları kavramak:

Diziler işaretçi değildir!

Her şeyden önce (ve yeterince vaaz edilmiştir), diziler işaretçi değildir . Bunun yerine, çoğu kullanımda, bir işaretçiye atanabilen ilk elemanlarına adrese 'bozunurlar':

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

int *p = a; // p now points to a[0]

Bu şekilde çalıştığını varsayıyorum, böylece dizinin içeriğine hepsini kopyalamadan erişilebilir. Bu sadece dizi türlerinin bir davranışıdır ve aynı şey olduklarını ima etme amacı taşımaz.



Çok boyutlu diziler

Çok boyutlu diziler, hafızayı derleyici / makinenin anlayabileceği ve üzerinde çalışabileceği bir şekilde 'bölümlemenin' bir yoludur.

Örneğin, int a[4][3][5]= tamsayı boyutlu belleğin 4 * 3 * 5 (60) 'parçalarını' içeren bir dizi.

int a[4][3][5]Düz vs kullanmanın avantajı int b[60], artık 'bölümlenmiş' olmalarıdır (gerekirse 'yığınlarıyla' çalışmak daha kolaydır) ve program artık bağlı denetim gerçekleştirebilir.

Aslında, int a[4][3][5]depolandığı aynen böyle int b[60]bellekte - tek fark, belli büyüklükteki ayrı varlıklar iseniz gibi program şimdi yönetir olduğunu (Özellikle, beş üç grup dört grup).

Unutmayın: Hem int a[4][3][5]ve int b[60]bellekte aynıdır, tek fark onlar uygulama / derleyici tarafından ele konum nasıl

{
  {1, 2, 3, 4, 5}
  {6, 7, 8, 9, 10}
  {11, 12, 13, 14, 15}
}
{
  {16, 17, 18, 19, 20}
  {21, 22, 23, 24, 25}
  {26, 27, 28, 29, 30}
}
{
  {31, 32, 33, 34, 35}
  {36, 37, 38, 39, 40}
  {41, 42, 43, 44, 45}
}
{
  {46, 47, 48, 49, 50}
  {51, 52, 53, 54, 55}
  {56, 57, 58, 59, 60}
}

Buradan, her "bölümün" sadece programın izlediği bir dizi olduğunu açıkça görebilirsiniz.



Sözdizimi

Şimdi diziler sözdizimsel olarak işaretçilerden farklıdır . Bu, özellikle derleyicinin / makinenin onlara farklı davranacağı anlamına gelir . Bu hiç akıllıca görünmeyebilir, ancak şuna bir bakın:

int a[3][3];

printf("%p %p", a, a[0]);

Yukarıdaki örnek aynı bellek adresini şu şekilde iki kez yazdırır:

0x7eb5a3b4 0x7eb5a3b4

Ancak, bir işaretçiye yalnızca biri doğrudan atanabilir :

int *p1 = a[0]; // RIGHT !

int *p2 = a; // WRONG !

Neden a bir işaretçiye atanamıyor ama atanabiliyor a[0] ?

Bu, basitçe, çok boyutlu dizilerin bir sonucudur ve nedenini açıklayacağım:

' a' Düzeyinde, dört gözle bekleyeceğimiz başka bir 'boyutumuz olduğunu görüyoruz. a[0]Bununla birlikte, ' ' seviyesinde, zaten en üst boyuttayız, bu nedenle program söz konusu olduğunda, sadece normal bir diziye bakıyoruz.

Şunları soruyor olabilirsiniz:

Dizinin bir işaretçi yapma açısından çok boyutlu olması neden önemlidir?

Bu şekilde düşünmek en iyisidir:

Çok boyutlu bir diziden gelen bir 'bozulma' sadece bir adres değil, aynı zamanda birinci boyutun ötesinde dizi tarafından belirlenen sınırlardan oluşan bölüm verileri içeren bir adrestir (AKA, yine de temel verilerinin diğer dizilerden yapıldığını anlar).

Bu 'bölümleme' mantığı, biz belirtmedikçe bir işaretçi içinde var olamaz:

int a[4][5][95][8];

int (*p)[5][95][8];

p = a; // p = *a[0] // p = a+0

Aksi takdirde, dizinin sıralama özelliklerinin anlamı kaybolur.

Ayrıca, etrafındaki parantez kullanımına dikkat edin *p: int (*p)[5][95][8]- Bu, bu sınırlara sahip bir işaretçi dizisi değil, bu sınırlarla bir işaretçi yaptığımızı belirtmek içindir:int *p[5][95][8]



Sonuç

Hadi gözden geçirelim:

  • Diziler, kullanılan bağlamda başka bir amaçları yoksa adreslere bozulur.
  • Çok boyutlu diziler yalnızca dizilerden oluşan dizilerdir - Bu nedenle, "bozulmuş" adres "alt boyutlarım var" yükünü taşıyacaktır.
  • Siz ona vermediğiniz sürece boyut verileri bir işaretçide var olamaz .

Kısaca: çok boyutlu diziler, içeriklerini anlama yeteneği taşıyan adreslere bozunur.


1
Cevabın ilk kısmı harika, ancak ikincisi değil. Bu doğru değil: int *p1 = &(a[0]); // RIGHT !aslında aynıint *p1 = a;
2501

@ 2501 Bu hatayı tespit ettiğiniz için teşekkür ederim, düzeltdim. Bu "kuralı" tanımlayan örneğin neden buna meydan okuduğunu kesin olarak söyleyemem. Yeniden ifade etmeye değer şudur ki, iki varlık işaretçi olarak yorumlanabiliyor ve aynı değeri veriyorsa, aynı anlamı taşıdıkları anlamına gelmez.
Super Cat

7

İçinde

int *ptr= l_matrix[0];

beğenebilirsin

*p
*(p+1)
*(p+2)

tüm 2 boyutlu diziler de 1-d olarak saklanır.


5

İyi günler,

Deklarasyon

static uint8_t l_matrix[10][20];

10 sıra 20 unit8_t lokasyon, yani 200 uint8_t boyutlu lokasyon için bir kenara depolama ayırmıştır, her eleman 20 x satır + sütun hesaplanarak bulunur.

Yani değil

uint8_t (*matrix_ptr)[20] = l_matrix;

size ihtiyacınız olanı verecek ve dizinin ilk satırının sütun sıfır öğesini gösterecek mi?

Düzenleme: Bunu biraz daha düşünürsek, bir dizi adı tanım gereği bir işaretçi değil mi? Yani, bir dizinin adı, ilk elemanın konumu ile eşanlamlıdır, yani l_matrix [0] [0]?

Düzenleme2: Başkalarının da bahsettiği gibi, yorum alanı daha fazla tartışma için biraz fazla küçük. Neyse:

typedef uint8_t array_of_20_uint8_t[20];
array_of_20_uint8_t *matrix_ptr = l_matrix;

söz konusu dizi için herhangi bir depolama tahsisi sağlamaz.

Yukarıda bahsedildiği ve standart tarafından tanımlandığı gibi, ifade:

static uint8_t l_matrix[10][20];

uint8_t türünde 200 ardışık konum ayırdı.

Formun ifadelerini kullanarak l_matrix'e atıfta bulunarak:

(*l_matrix + (20 * rowno) + colno)

size rowno satırında bulunan colno'th elemanının içeriğini verecektir.

Tüm işaretçi manipülasyonları, işaret edilen nesnenin boyutunu otomatik olarak hesaba katar. - K&R Bölüm 5.4, s.103

Bu, aynı zamanda, eldeki nesnenin depolanmasında herhangi bir dolgu veya bayt hizalama kayması söz konusu olduğunda da geçerlidir. Derleyici bunları otomatik olarak ayarlayacaktır. C ANSI standardının tanımı gereği.

HTH

şerefe,


1
uint8_t (* matrix_ptr) [] [20] << ilk parantezler dışarıda bırakılmalıdır, doğru uint8_t (* matrix_ptr) [20]
Aconcagua

5

C99'da (clang ve gcc tarafından desteklenir), çok boyutlu dizileri başvuruya göre işlevlere geçirmek için belirsiz bir sözdizimi vardır:

int l_matrix[10][20];

void test(int matrix_ptr[static 10][20]) {
}

int main(void) {
    test(l_matrix);
}

Düz bir göstericiden farklı olarak, bu dizi boyutu hakkında ipuçları verir ve teorik olarak derleyicinin çok küçük dizileri geçme konusunda uyarıda bulunmasına ve sınırların dışındaki erişimi açıkça görmesine izin verir.

Ne yazık ki, düzeltilmiyor sizeof()ve derleyiciler henüz bu bilgiyi kullanmıyor gibi görünüyor, bu yüzden merak konusu olmaya devam ediyor.


1
Bu yanıt yanıltıcıdır: Bu, argümanı sabit boyutlu bir dizi yapmaz, yine de bir göstericidir. en az 10 öğenin mevcut static 10olduğuna dair bir tür garantidir , bu da boyutun sabit olmadığı anlamına gelir.
bluss

1
@bluss soru bir işaretçi hakkındaydı, bu yüzden bir işaretçi ile cevaplamanın ( referansla not ederek ) nasıl yanıltıcı olduğunu görmüyorum . Dizi, işlev açısından sabit boyuttadır, çünkü bu sınırların dışındaki öğelere erişim tanımsızdır.
Kornel

10'un ötesine erişimin tanımsız olduğunu düşünmüyorum, bunu gösteren hiçbir şey göremiyorum.
bluss

Bu yanıt, anahtar kelime olmadan staticdizinin referans olarak aktarılmayacağını öne sürüyor gibi görünüyor , bu doğru değil. Diziler yine de referans olarak aktarılır. Orijinal soru, farklı bir kullanım durumu hakkında soruldu - aynı işlev / ad alanı içinde ek bir işaretçi kullanarak 2B dizinin öğelerine erişim.
Palo

4

Diziyi doğrusal olarak bildirip (satır, sütun) dizi indeksi hesaplamasını kendiniz yaparak derleyici ile uğraşmaktan her zaman kaçınabilirsiniz.

static uint8_t l_matrix[200];

void test(int row, int col, uint8_t val)

{

   uint8_t* matrix_ptr = l_matrix;
   matrix_ptr [col+y*row] = val; // to assign a value

}

bu, derleyicinin zaten yapacağı şeydir.


1
C derleyicisinin yaptığı da budur. [] Notasyonu işaretçi aritmetik için sadece sözdizimsel şeker - K gerçekten "Dizi" teriminin gerçek düşüncesini yok
Ken Keenan

7
Bu çözümün, bunu yapmanın doğru yolunu asla bulamama dezavantajı vardır.
Craig McQueen

2

Çok boyutlu diziye işaret eden işaretçiyi başlatmanın temel sözdizimi şudur:

type (*pointer)[1st dimension size][2nd dimension size][..] = &array_name

Bunu aramak için temel sözdizimi

(*pointer_name)[1st index][2nd index][...]

İşte bir örnek:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
   // The multidimentional array...
   char balance[5][100] = {
       "Subham",
       "Messi"
   };

   char (*p)[5][100] = &balance; // Pointer initialization...

   printf("%s\n",(*p)[0]); // Calling...
   printf("%s\n",(*p)[1]); // Calling...

  return 0;
}

Çıktı:

Subham
Messi

İşe yaradı...


1

Bunu şu şekilde yapabilirsiniz:

uint8_t (*matrix_ptr)[10][20] = &l_matrix;

1
bu 10 * 20 bayt ram işgal etmez mi? (bir mikro-im)
dereotu

4 bayt veya kutunuzda bir işaretçi ne kadar büyük olursa olsun kaplar. Ancak buna sahipseniz, matrix_ptr [0] [x] [y] veya (* matrix_ptr) [x] [y] ile indekslemeniz gerektiğini unutmayın. "İki boyutlu diziye işaretçi" nin doğrudan ve kelime kelime yorumu: p
Johannes Schaub - litb

Teşekkürler litb, nasıl erişeceğimi söylemeyi unuttum. Cevabınızla harika bir iş çıkardığınız için cevabımı düzenlemenin bir anlamı yok :)
Nick Dandoulakis

Öyleyse, bu 10 * 20 bayt RAM kaplıyor mu, yok mu?
Danijel

@Danijel, iki boyutlu diziye bir işaretçi olduğu için, yalnızca 4 bayt veya kutunuzda bir işaretçi hangi boyutta olursa olsun, yani 16 bit, 32 bit, 64 bit vb.
Kaplar

1

İlk elemana bir işaretçi istiyorsunuz, bu yüzden;

static uint8_t l_matrix[10][20];

void test(){
   uint8_t *matrix_ptr = l_matrix[0]; //wrong idea 
}

0

Negatif dizinler kullanmak istiyorsanız bir ofset de ekleyebilirsiniz:

uint8_t l_matrix[10][20];
uint8_t (*matrix_ptr)[20] = l_matrix+5;
matrix_ptr[-4][1]=7;

Derleyiciniz bir hata veya uyarı verirse, kullanabilirsiniz:

uint8_t (*matrix_ptr)[20] = (uint8_t (*)[20]) l_matrix;

Merhaba. Bu soru ile etiketlendiğinden cyanıt aynı dilde olmalıdır. Lütfen etiketlere dikkat edin.
2501
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.