Array dan pointer adalah dua hal yang berbeda di C, tapi sebagian besar waktu perilakunya sama. "Sebagian besar waktu" itulah yang membuat topik ini licin: kode yang memperlakukan array sebagai pointer biasanya bekerja, sampai suatu hari kode itu masuk ke salah satu dari dua tempat di mana perbedaannya penting, dan jawabannya diam-diam salah.
Halaman ini menarik garis pembatasnya dengan tepat.
Array decay: satu aturan di balik semuanya
Di hampir setiap ekspresi, nama array otomatis dikonversi menjadi pointer ke elemen pertamanya. Ini disebut decay. arr menjadi &arr[0], dengan tipe int *.
Nama array dan alamat elemen pertamanya tercetak sama persis. Itulah decay yang sedang bekerja, dan karena itulah kamu bisa memberikan array ke pointer tanpa &.
Hanya ada tepat tiga tempat decay tidak terjadi: sebagai operand sizeof, sebagai operand &, dan saat menginisialisasi array karakter dari string literal. Di tempat lain, anggap saja decay terjadi.
arr[i] secara harfiah adalah *(arr + i)
Standar C mendefinisikan operator subscript lewat aritmetika pointer: a[b] berarti *(a + b). Pengindeksan bukan fitur terpisah: itu hanya singkatan.
Ada dua akibatnya. Pertama, pointer bisa diberi subscript: p[2] adalah *(p + 2), dan karena itulah fungsi yang menerima pointer tetap bisa memakai sintaks arr[i] yang sudah akrab. Kedua, 2[arr] bisa dikompilasi: *(2 + arr) adalah alamat yang sama dengan *(arr + 2). Tidak ada yang menulisnya selain di kuis, tapi itu menjawab pertanyaan apakah pengindeksan "sebenarnya" adalah aritmetika pointer.
Mengirim array ke fungsi
Karena decay, sebuah fungsi tidak pernah menerima array. Fungsi menerima pointer.
Ada tiga hal yang layak diambil dari contoh itu.
Ukuran di int arr[100] hanyalah dokumentasi, tidak lebih. Compiler menulis ulang parameternya menjadi int *arr dan tidak pernah memeriksa apakah kamu benar-benar mengirim 100 elemen.
Panjangnya harus dikirim terpisah. Fungsi hanya punya alamat dan tidak tahu seberapa jauh datanya membentang. Karena itulah hampir semua fungsi C yang menerima array juga menerima jumlah: memcpy(dst, src, n), fread(ptr, size, count, f), qsort(base, nmemb, size, cmp). String adalah pengecualian hanya karena membawa penanda akhirnya sendiri, yaitu '\0'.
Mengirim pointer itu murah dan datanya dipakai bersama. Tidak ada salinan array yang dibuat, jadi fungsi bisa mengubah elemen milik pemanggil, dan sering kali memang itulah yang kamu inginkan:
Tandai parameter yang hanya dibaca dengan const int * seperti yang dilakukan print_all. Itu mendokumentasikan maksudnya dan membuat compiler bisa menangkap penulisan yang tidak disengaja.
Jebakan sizeof
Inilah bug yang pernah menjebak setiap programmer C sekali. sizeof adalah salah satu dari dua tempat decay tidak terjadi, jadi operator itu memberi ukuran array yang sebenarnya di tempat array itu dideklarasikan, dan ukuran sebuah pointer di mana pun array itu sudah mengalami decay.
Di main, sizeof data bernilai 40 byte dan panjangnya terhitung 10. Di dalam inspect, sizeof arr adalah ukuran pointer, 8 di mesin 64-bit, dan "panjangnya" keluar sebagai 2. Kodenya kelihatan sama persis dan diam-diam salah.
Aturan yang mengikutinya: hitung panjang array hanya di scope tempat array itu dideklarasikan, lalu kirim nilai itu dari sana.
& pada array bukan tipe yang sama
Tempat lain decay tidak terjadi adalah operator &. &arr adalah pointer ke seluruh array, bukan ke elemen pertamanya. Keduanya menyimpan alamat yang sama; bedanya ada pada arti "tambah 1".
Alamat awalnya sama, langkahnya berbeda. arr bertipe int * dan melangkah satu elemen; &arr bertipe int (*)[5] dan melangkah sebesar seluruh array. Inilah mekanisme yang membuat pengiriman satu baris array 2D bisa bekerja, yang dibahas di array multidimensi.
Pointer ke array vs array berisi pointer
Dua deklarasi yang kelihatan mirip tapi artinya berlawanan:
int *p[5]; // array of 5 pointers to int
int (*q)[5]; // pointer to an array of 5 ints
[] punya prioritas lebih tinggi daripada *, jadi int *p[5] dibaca sebagai "p adalah array, berisi pointer". Tanda kurung di int (*q)[5] memaksa * terikat lebih dulu: "q adalah pointer, ke sebuah array".
int *p[5] int (*q)[5]
p[0] -> [ int ] q -> [ int | int | int | int | int ]
p[1] -> [ int ] one contiguous block of 5
p[2] -> [ int ]
p[3] -> [ int ]
p[4] -> [ int ]
five separate addresses
Array berisi pointer adalah yang umum, dan kamu sudah pernah memakainya: char *argv[] adalah array berisi pointer ke string, satu untuk setiap argumen baris perintah. Begitu juga caramu menyimpan daftar string yang panjangnya berbeda-beda tanpa membuang tempat.
Perhatikan (*q)[0]: dereference pointernya untuk mendapat array, lalu indeks array itu. Tanda kurungnya lagi-lagi wajib, dengan alasan prioritas yang sama.
Di mana array dan pointer benar-benar berbeda
Ingat tabel singkat ini dan kebingungannya akan hilang:
Array int arr[10] | Pointer int *p | |
|---|---|---|
| Apa itu | 10 int yang tersusun di memori | satu variabel yang menyimpan alamat |
sizeof | 40: seluruh blok | 8: hanya pointernya |
| Bisa diberi nilai baru | tidak: arr = x; adalah error | ya: p = x; tidak masalah |
& menghasilkan | int (*)[10] | int ** |
| Asal memorinya | dideklarasikan bersama array | ke mana pun kamu mengarahkannya |
Nama array bukan variabel yang menyimpan alamat: nama array adalah tempat penyimpanannya, dan alamatnya dihitung saat dibutuhkan. Karena itulah kamu tidak bisa memberinya nilai baru.
Pertanyaan yang sering diajukan
Apakah array dan pointer itu sama di C?
Tidak, tapi keduanya mudah tertukar karena nama array berubah (decay) menjadi pointer ke elemen pertamanya di hampir setiap ekspresi. Array sendiri adalah blok elemen dengan ukuran yang diketahui; pointer adalah satu variabel yang menyimpan satu alamat. sizeof dan & adalah dua tempat perbedaannya terlihat.
Kenapa sizeof(arr) memberi jawaban yang salah di dalam fungsi?
Karena parameternya bukan array. void f(int arr[]) diam-diam ditulis ulang menjadi void f(int *arr), jadi sizeof(arr) mengukur sebuah pointer (8 byte di kebanyakan mesin), bukan array aslinya. Kirim panjangnya sebagai parameter terpisah.
Apa bedanya int *p[5] dan int (*p)[5]?
int *p[5] adalah array berisi 5 pointer ke int. int (*p)[5] adalah satu pointer ke array berisi 5 int. Tanda kurung mengikat * ke nama lebih dulu; tanpa tanda kurung, [] yang menang karena prioritasnya lebih tinggi.
Apakah arr[i] benar-benar sama dengan *(arr + i)?
Ya: standar C mendefinisikan a[b] sebagai *(a + b). Karena itu juga i[arr] yang kelihatan aneh bisa dikompilasi dan bekerja: penjumlahan bersifat komutatif, jadi *(i + arr) adalah elemen yang sama. Jangan pernah menulisnya di kode sungguhan, tapi itu membuktikan aturannya.