Buat penunjuk ke array dua dimensi

120

Saya memerlukan penunjuk ke array 2 dimensi statis. Bagaimana ini dilakukan?

static uint8_t l_matrix[10][20];

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

Saya mendapatkan semua jenis kesalahan seperti:

  • peringatan: tugas dari tipe penunjuk yang tidak kompatibel
  • nilai yang disubscribe bukanlah array atau pointer
  • kesalahan: penggunaan anggota larik fleksibel tidak valid
Dil
sumber
Baca stackoverflow.com/questions/423823/… ini dapat membantu Anda
Johannes Schaub - litb
1
@ JohannesSchaub-litb Itu sudah tidak ada lagi. (Bagaimana cara melihatnya lagi ...? Saya tahu anggota bereputasi rendah dapat melihatnya, tetapi saya lupa caranya ...)
Mateen Ulhaq
1
@muntoo: Ini salinannya: gist.github.com/sharth/ede13c0502d5dd8d45bd
Bill Lynch

Jawaban:

140

Di sini Anda ingin membuat penunjuk ke elemen pertama dari array

uint8_t (*matrix_ptr)[20] = l_matrix;

Dengan typedef, ini terlihat lebih bersih

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

Kemudian Anda bisa menikmati hidup lagi :)

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

Waspadalah terhadap dunia pointer / array di C, banyak kebingungan seputar ini.


Sunting

Meninjau beberapa jawaban lain di sini, karena kolom komentar terlalu pendek untuk dilakukan di sana. Beberapa alternatif diusulkan, tetapi tidak ditunjukkan bagaimana mereka berperilaku. Inilah cara mereka melakukannya

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

Jika Anda memperbaiki kesalahan dan menambahkan alamat-operator &seperti di cuplikan berikut

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

Kemudian yang satu itu membuat pointer ke tipe array yang tidak lengkap dari elemen tipe array 20 uint8_t. Karena penunjuk adalah ke array array, Anda harus mengaksesnya dengan

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

Dan karena ini adalah penunjuk ke larik yang tidak lengkap, Anda tidak dapat melakukannya sebagai pintasan

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

Karena pengindeksan memerlukan ukuran jenis elemen untuk diketahui (pengindeksan menyiratkan penambahan bilangan bulat ke penunjuk, sehingga tidak akan berfungsi dengan jenis yang tidak lengkap). Perhatikan bahwa ini hanya berfungsi di C, karena T[]dan T[N]merupakan tipe yang kompatibel. C ++ tidak memiliki konsep tipe yang kompatibel , sehingga akan menolak kode tersebut, karena T[]dan T[10]merupakan tipe yang berbeda.


Alternatif berikut tidak berfungsi sama sekali, karena tipe elemen dari larik, saat Anda melihatnya sebagai larik satu dimensi, bukan uint8_t , tetapiuint8_t[20]

uint8_t *matrix_ptr = l_matrix; // fail

Berikut ini adalah alternatif yang bagus

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

Anda mengaksesnya dengan

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

Ini memiliki keuntungan karena mempertahankan ukuran dimensi luar. Jadi Anda bisa menerapkan ukuran di atasnya

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

Ada satu jawaban lain yang memanfaatkan fakta bahwa item dalam array disimpan secara berdekatan

uint8_t *matrix_ptr = l_matrix[0];

Sekarang, secara formal hanya memungkinkan Anda untuk mengakses elemen elemen pertama dari array dua dimensi. Artinya, kondisi berikut berlaku

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

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

Anda akan melihatnya mungkin berhasil 10*20-1, tetapi jika Anda menggunakan analisis alias dan pengoptimalan agresif lainnya, beberapa kompilator dapat membuat asumsi yang dapat merusak kode itu. Karena itu, saya tidak pernah menemukan kompiler yang gagal di dalamnya (tapi sekali lagi, saya belum menggunakan teknik itu dalam kode sebenarnya), dan bahkan C FAQ memiliki teknik itu (dengan peringatan tentang UB'ness-nya) ), dan jika Anda tidak dapat mengubah tipe array, ini adalah opsi terakhir untuk menyelamatkan Anda :)

Johannes Schaub - litb
sumber
+1 - info bagus tentang kerusakan int (*) [] [20] - tidak dapat melakukannya di C ++
Faisal Vali
@ litb, saya minta maaf tetapi ini salah karena solusi Anda tidak menyediakan alokasi penyimpanan apa pun untuk array.
Rob Wells
2
@ Rob, saya tidak begitu mengerti Anda. penyimpanan dalam semua kasus ini disediakan oleh l_matix itu sendiri. Pointer ke mereka mengambil penyimpanan dari mana pun mereka dideklarasikan di dan sebagai (stack, segmen data statis, ...).
Johannes Schaub - litb
Hanya ingin tahu mengapa kita membutuhkan alamat "&" dari l_matrix?
elektro
1
@Sohaib - tidak, itu hanya membuat satu penunjuk. Anda mungkin bingung dengan uint8_t *d[20], yang membuat larik 3 pointer ke uint8_t, tetapi itu tidak akan berfungsi dalam kasus ini.
Palo
28

Untuk memahami ini sepenuhnya , Anda harus memahami konsep berikut:

Array bukanlah penunjuk!

Pertama-tama (Dan sudah cukup diberitakan), array bukanlah pointer . Sebaliknya, di sebagian besar penggunaan, mereka 'membusuk' ke alamat ke elemen pertama mereka, yang dapat ditetapkan ke penunjuk:

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

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

Saya berasumsi ini bekerja dengan cara ini sehingga konten array dapat diakses tanpa menyalin semuanya. Itu hanya perilaku tipe array dan tidak dimaksudkan untuk menyiratkan bahwa keduanya adalah hal yang sama.



Array multidimensi

Array multidimensi hanyalah cara untuk 'mempartisi' memori dengan cara yang dapat dipahami dan dioperasikan oleh kompiler / mesin.

Misalnya, int a[4][3][5] = sebuah array yang berisi 4 * 3 * 5 (60) 'potongan' memori berukuran integer.

Keuntungan menggunakan int a[4][3][5]vs biasaint b[60] adalah sekarang mereka 'dipartisi' (Lebih mudah untuk bekerja dengan 'chunks' mereka, jika diperlukan), dan program sekarang dapat melakukan pemeriksaan terikat.

Faktanya, int a[4][3][5]disimpan persis seperti int b[60]di memori - Satu- satunya perbedaan adalah bahwa program sekarang mengelolanya seolah-olah mereka adalah entitas terpisah dengan ukuran tertentu (Secara khusus, empat grup dari tiga grup lima).

Perlu diingat: Keduanya int a[4][3][5]dan int b[60]sama dalam memori, dan satu-satunya perbedaan adalah bagaimana mereka ditangani oleh aplikasi / kompiler

{
  {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}
}

Dari sini, Anda dapat dengan jelas melihat bahwa setiap "partisi" hanyalah sebuah array yang dipantau oleh program.



Sintaksis

Sekarang, array secara sintaksis berbeda dari pointer . Secara khusus, ini berarti kompilator / mesin akan memperlakukannya secara berbeda. Ini mungkin tampak tidak sulit, tapi lihat ini:

int a[3][3];

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

Contoh di atas mencetak alamat memori yang sama dua kali, seperti ini:

0x7eb5a3b4 0x7eb5a3b4

Namun, hanya satu yang dapat ditetapkan ke penunjuk secara langsung :

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

int *p2 = a; // WRONG !

Mengapa tidak a bisa ditetapkan ke penunjuk tapi a[0] bisa?

Ini, sederhananya, adalah konsekuensi dari array multidimensi, dan saya akan menjelaskan alasannya:

Pada level ' a', kami masih melihat bahwa kami memiliki 'dimensi' lain untuk dinantikan. Pada tingkatan 'a[0] ', bagaimanapun, kita sudah berada di dimensi teratas, sejauh menyangkut program kita hanya melihat array normal.

Anda mungkin bertanya:

Mengapa penting jika array multidimensi dalam hal membuat penunjuk untuknya?

Cara terbaik untuk berpikir seperti ini:

Sebuah 'decay' dari array multidimensi bukan hanya sebuah alamat, tetapi sebuah alamat dengan data partisi (AKA masih memahami bahwa data dasarnya terbuat dari array lain), yang terdiri dari batas-batas yang ditetapkan oleh array di luar dimensi pertama.

Logika 'partisi' ini tidak bisa ada di dalam pointer kecuali kita menentukannya:

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

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

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

Jika tidak, arti properti pengurutan larik akan hilang.

Perhatikan juga penggunaan tanda kurung di sekitar *p: int (*p)[5][95][8]- Itu untuk menentukan bahwa kami membuat pointer dengan batas-batas ini, bukan array pointer dengan batas-batas ini:int *p[5][95][8]



Kesimpulan

Mari kita ulas:

  • Array membusuk ke alamat jika tidak ada tujuan lain dalam konteks yang digunakan
  • Larik multidimensi hanyalah larik larik - Oleh karena itu, alamat yang 'membusuk' akan membawa beban "Saya memiliki sub dimensi"
  • Data dimensi tidak bisa ada di penunjuk kecuali Anda memberikannya .

Singkatnya: array multidimensi membusuk ke alamat yang membawa kemampuan untuk memahami isinya.

Kucing Super
sumber
1
Bagian pertama dari jawabannya bagus, tapi yang kedua tidak. Ini tidak benar:, int *p1 = &(a[0]); // RIGHT !sebenarnya ini identik denganint *p1 = a;
2501
@ 2501 Terima kasih telah melihat kesalahan itu, saya telah memperbaikinya. Saya tidak dapat mengatakan dengan pasti mengapa contoh yang mendefinisikan "aturan" ini juga menentangnya. Layak untuk dinyatakan kembali adalah bahwa hanya karena dua entitas dapat diartikan sebagai petunjuk dan menghasilkan nilai yang sama, tidak berarti keduanya memiliki arti yang sama.
Super Cat
7

Di

int *ptr= l_matrix[0];

Anda dapat mengakses suka

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

setelah semua array 2 dimensi juga disimpan sebagai 1-d.

Sagar
sumber
5

G'day,

Deklarasi

static uint8_t l_matrix[10][20];

telah menyisihkan penyimpanan untuk 10 baris dari 20 lokasi unit8_t, yaitu 200 lokasi berukuran uint8_t, dengan tiap elemen ditemukan dengan menghitung 20 x baris + kolom.

Jadi tidak

uint8_t (*matrix_ptr)[20] = l_matrix;

memberi Anda apa yang Anda butuhkan dan arahkan ke elemen kolom nol dari baris pertama dari array?

Sunting: Berpikir tentang ini sedikit lebih jauh, bukankah nama array, menurut definisi, sebuah pointer? Artinya, nama sebuah array adalah sinonim untuk lokasi elemen pertama, yaitu l_matrix [0] [0]?

Sunting2: Seperti yang disebutkan oleh orang lain, ruang komentar agak terlalu kecil untuk diskusi lebih lanjut. Bagaimanapun:

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

tidak menyediakan alokasi penyimpanan apa pun untuk larik yang dimaksud.

Seperti disebutkan di atas, dan seperti yang didefinisikan oleh standar, pernyataan:

static uint8_t l_matrix[10][20];

telah menyisihkan 200 lokasi berurutan tipe uint8_t.

Mengacu ke l_matrix menggunakan pernyataan bentuk:

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

akan memberi Anda konten elemen warna yang ditemukan di baris rowno.

Semua manipulasi penunjuk secara otomatis memperhitungkan ukuran objek yang dituju. - K&R Bagian 5.4, hlm. 103

Hal ini juga terjadi jika padding atau pergeseran perataan byte terlibat dalam penyimpanan objek yang ada. Kompiler akan secara otomatis menyesuaikan ini. Menurut definisi standar C ANSI.

HTH

Bersulang,

Rob Wells
sumber
1
uint8_t (* matrix_ptr) [] [20] << tanda kurung pertama harus dihilangkan, yang benar adalah uint8_t (* matrix_ptr) [20]
Aconcagua
5

Di C99 (didukung oleh clang dan gcc) ada sintaks yang tidak jelas untuk meneruskan array multi-dimensi ke fungsi dengan referensi:

int l_matrix[10][20];

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

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

Tidak seperti pointer biasa, ini mengisyaratkan tentang ukuran array, secara teoritis memungkinkan compiler untuk memperingatkan tentang melewatkan array yang terlalu kecil dan menemukan akses di luar batas yang jelas.

Sayangnya, itu tidak memperbaiki sizeof()dan penyusun tampaknya belum menggunakan informasi itu, jadi tetap menjadi rasa ingin tahu.

Kornel
sumber
1
Jawaban ini menyesatkan: Itu tidak membuat argumen menjadi larik ukuran tetap, itu masih sebuah penunjuk. static 10adalah semacam jaminan bahwa setidaknya ada 10 elemen, yang sekali lagi berarti ukurannya tidak tetap.
bluss
1
@ bluss pertanyaannya adalah tentang pointer, jadi saya tidak melihat bagaimana menjawab dengan pointer (mencatat dengan referensi ) itu menyesatkan. Larik memiliki ukuran tetap dari perspektif fungsi, karena akses ke elemen di luar batas ini tidak ditentukan.
Kornel
Saya tidak berpikir akses di luar 10 tidak ditentukan, saya tidak dapat melihat apa pun yang menunjukkan itu.
bluss
Jawaban ini sepertinya menyarankan bahwa tanpa kata kunci static, array tidak akan diteruskan oleh referensi, yang mana tidak benar. Array diteruskan dengan referensi. Pertanyaan asli menanyakan tentang kasus penggunaan yang berbeda - mengakses elemen array 2D menggunakan penunjuk tambahan dalam fungsi / namespace yang sama.
Palo
4

Anda selalu dapat menghindari mengutak-atik compiler dengan mendeklarasikan array sebagai linear dan melakukan kalkulasi indeks (row, col) ke array sendiri.

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

}

inilah yang akan dilakukan oleh kompilator.

gnosis
sumber
1
Inilah yang dilakukan oleh kompilator C. C tidak benar-benar memiliki gagasan nyata tentang "array" - notasi [] hanyalah gula sintaksis untuk aritmatika penunjuk
Ken Keenan
7
Solusi ini memiliki kelemahan karena tidak pernah menemukan cara yang tepat untuk melakukannya.
Craig McQueen
2

Sintaks dasar dari menginisialisasi pointer yang mengarah ke array multidimentional adalah

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

Sintaks dasar untuk memanggilnya adalah

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

Berikut ini contohnya:

#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;
}

Outputnya adalah:

Subham
Messi

Berhasil ...

Subham Debnath
sumber
1

Anda bisa melakukannya seperti ini:

uint8_t (*matrix_ptr)[10][20] = &l_matrix;
Nick Dandoulakis
sumber
1
bukankah ini menempati 10 * 20 byte dari ram? (Saya menggunakan mikrokontroler)
Dill
Ini akan menempati 4 byte atau berapapun ukuran pointer besar di kotak Anda. Tetapi ingat jika Anda memiliki yang ini, Anda harus mengindeks dengan matrix_ptr [0] [x] [y] atau (* matrix_ptr) [x] [y]. Ini adalah interpretasi langsung dan kata demi kata dari "penunjuk ke array dua dimensi": p
Johannes Schaub - litb
Terima kasih litb, saya lupa menyebutkan cara mengaksesnya. Tidak ada gunanya mengedit jawaban saya karena Anda melakukan pekerjaan yang hebat dengan jawaban Anda :)
Nick Dandoulakis
Jadi, apakah ini menempati RAM 10 * 20 byte atau tidak?
Danijel
@Danijel, karena ini adalah penunjuk ke larik dua dimensi, ia hanya akan menempati 4 byte atau berapapun ukuran penunjuk di kotak Anda, yaitu 16-bit, 32-bit, 64-bit, dll.
Nick Dandoulakis
1

Anda ingin pointer ke elemen pertama, jadi;

static uint8_t l_matrix[10][20];

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

Anda juga dapat menambahkan offset jika Anda ingin menggunakan indeks negatif:

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

Jika kompiler Anda memberikan kesalahan atau peringatan, Anda dapat menggunakan:

uint8_t (*matrix_ptr)[20] = (uint8_t (*)[20]) l_matrix;
mathengineer
sumber
Halo. Pertanyaan ini diberi tag c, jadi jawabannya harus dalam bahasa yang sama. Harap perhatikan tagnya.
2501