Array adalah blok berukuran tetap yang berisi elemen bertipe sama, disusun berurutan di memori. Susunan yang bersambung inilah intinya: karena itu akses lewat indeks langsung cepat, array dan pointer sangat erat hubungannya, dan C bisa memberimu alamat elemen ke-nol lalu membiarkanmu menemukan elemen lain dengan aritmetika.
Mendeklarasikan Array
Tipe elemen, nama, dan ukuran di dalam kurung siku:
int scores[5]; // 5 ints
double prices[100]; // 100 doubles
char initials[3]; // 3 chars
Ukurannya adalah jumlah elemen, dan harus sudah diketahui di tempat array dideklarasikan. Biasanya berupa literal atau konstanta yang dibuat dengan #define. Memorinya langsung disediakan saat itu juga; tidak ada yang dialokasikan belakangan.
Array lokal yang baru dideklarasikan berisi nilai sampah, bukan nol:
Baris kedua bisa mencetak nol, bisa mencetak angka yang sangat besar, dan bisa berbeda di setiap run atau antara build debug dan build release. Membaca nilai-nilai itu adalah undefined behavior; inisialisasi dulu sebelum membacanya.
Inisialisasi
Ada beberapa bentuk, semuanya berguna:
Pola {0} adalah yang paling perlu diingat: setiap elemen yang tidak kamu tulis akan diinisialisasi dengan nol, jadi satu angka nol membuat seluruh array bernilai nol. Designated initializer ([4] = 99) memungkinkanmu mengisi posisi tertentu dan membiarkan sisanya nol, yang praktis untuk tabel pencarian yang sebagian besar kosong.
Satu hal yang tidak bisa kamu lakukan adalah meng-assign array setelah dideklarasikan:
int a[3] = {1, 2, 3};
int b[3];
b = a; // error: an array is not assignable
memcpy(b, a, sizeof a); // this is how you copy one
Indeks Dimulai dari Nol
Elemen pertama adalah arr[0] dan yang terakhir adalah arr[n-1]. Tidak ada arr[n].
Jalankan perulangan terakhir itu dan perhatikan alamatnya: setiap alamat berjarak tepat sizeof(int) byte dari alamat sebelumnya. Elemen-elemennya memang tersusun berdampingan:
index 0 1 2 3 4
+--------+--------+--------+--------+--------+
value | 88 | 95 | 75 | 60 | 100 |
+--------+--------+--------+--------+--------+
offset +0 +4 +8 +12 +16 bytes
^
&scores[0], which is also what `scores` decays to
Indeks yang dimulai dari nol bukan pilihan asal. Dengan begitu, indeks menjadi offset dari awal array, persis yang dibutuhkan aritmetika alamat. scores[i] didefinisikan sebagai *(scores + i); lihat pointer dan array.
Mengetahui Panjang Array
C tidak menyimpan panjang array di mana pun saat program berjalan, tetapi compiler mengetahuinya, dan sizeof memberimu akses ke informasi itu:
Pola yang dipakai adalah sizeof(arr) / sizeof(arr[0]). Membagi dengan arr[0], bukan dengan sizeof(double) yang ditulis langsung, membuat baris itu tetap benar kalau kamu mengganti tipe elemennya.
Jebakannya: cara ini hanya berlaku di tempat array dideklarasikan. Kirim array ke sebuah fungsi, maka parameternya adalah pointer, sehingga sizeof justru mengukur pointer. Biasanya 8 byte, yang menghasilkan "panjang" 1 atau 2. Karena itulah setiap fungsi di C yang menerima array juga menerima jumlah elemennya:
Menelusuri Array dengan Perulangan
Bentuk standarnya adalah for loop dari 0 selama i < n:
Perhatikan i < n, bukan i <= n. Dengan n elemen, indeks yang valid adalah 0 sampai n - 1, jadi <= menjalankan satu putaran ekstra dan membaca melewati ujung array. Mengisi nilai awal hottest dan coldest dari temps[0], bukan dari 0, adalah kebiasaan lain yang patut ditiru: nilai maksimum yang dimulai dari nol diam-diam salah kalau semua datanya negatif.
Akses di Luar Batas Adalah Undefined Behavior
Bagian C inilah yang mengejutkan orang yang datang dari bahasa lain. Tidak ada pemeriksaan batas. Tidak saat kompilasi, tidak juga saat program berjalan. arr[10] pada array berisi lima elemen bukan error, melainkan perhitungan alamat yang dilakukan compiler tanpa komentar apa pun.
int arr[5] = {1, 2, 3, 4, 5};
arr[7] = 99; // writes 8 bytes past the end of the array
int x = arr[-1]; // reads before the start
Apa yang terjadi selanjutnya adalah undefined behavior, dan gejalanya sangat beragam, sayangnya:
- terlihat berjalan normal, karena memori yang tertimpa kebetulan tidak dipakai;
- variabel lain berubah tanpa alasan yang terlihat;
- program crash dengan segmentation fault, mungkin jauh setelahnya;
- perilakunya berbeda antara
-O0dan-O2.
"Terlihat berjalan normal" adalah yang paling berbahaya, karena artinya bug itu ikut dirilis. Tiga cara bertahan:
- Gunakan
i < ndi perulangan. Sebagian besar akses yang kebablasan adalah kesalahan off-by-one di kondisi perulangan. - Validasi indeks yang berasal dari input.
- Build dengan sanitizer selama pengembangan.
gcc -fsanitize=address -g prog.cmengubah sebagian besar akses kebablasan menjadi laporan langsung yang menyebutkan file, baris, dan array yang terlibat.
Array dengan Tipe Lain
Sintaks yang sama berlaku untuk tipe elemen apa pun, termasuk struct:
Baris char word[6] = "hello"; layak diperhatikan sebentar: string di C hanyalah array char yang byte bermakna terakhirnya adalah '\0'. Karena terminator itulah array ini butuh enam slot untuk lima huruf.
Sekilas tentang Variable-Length Array
C99 mengizinkan array yang ukurannya berupa nilai saat program berjalan:
int n = get_count();
int buffer[n]; // a variable-length array
Ada dua peringatan. Ukurannya tetap begitu array dibuat, jadi VLA bukan list yang bisa bertambah panjang. Lalu memorinya berasal dari stack, sehingga n yang besar atau dikendalikan penyerang bisa membuat stack meluap dan program crash. Karena itu banyak proyek, termasuk kernel Linux, melarang VLA sama sekali. Implementasi C11 juga tidak wajib mendukungnya.
Kalau ukurannya benar-benar baru diketahui saat program berjalan, alokasikan memori:
Perhatikan bahwa arr[i] terbaca sama saja, baik arr berupa array maupun pointer ke memori yang dialokasikan: sintaks indeks tidak peduli. Detail lengkapnya ada di memori dinamis.
Pertanyaan yang sering diajukan
Bagaimana cara mendeklarasikan array di C?
Tulis tipe elemen, nama, dan ukuran di dalam kurung siku: int scores[5]; menyediakan tempat untuk lima int. Ukurannya harus berupa konstanta yang sudah diketahui di tempat array dideklarasikan (kecuali pada variable-length array), dan elemennya belum diinisialisasi sampai kamu memberi nilai.
Bagaimana cara mengetahui panjang array di C?
sizeof(arr) / sizeof(arr[0]), yaitu total ukuran dalam byte dibagi ukuran satu elemen. Cara ini hanya berlaku di scope tempat array dideklarasikan: begitu array dikirim ke fungsi, ia berubah menjadi pointer dan sizeof justru mengukur pointer itu.
Apa yang terjadi kalau mengakses melewati ujung array di C?
Tidak ada yang menghentikanmu. C tidak memeriksa batas array, jadi arr[10] pada array berisi 5 elemen akan membaca atau menulis memori apa pun yang ada di sana. Itu undefined behavior: program bisa mencetak nilai sampah, merusak variabel lain, atau crash. Bisa juga terlihat berjalan normal, sampai suatu saat tidak.
Bagaimana cara mengisi semua elemen array dengan nol di C?
int arr[100] = {0};: setiap elemen yang tidak kamu tulis akan diinisialisasi dengan nol, jadi satu angka nol sudah membuat seluruh array bernilai nol. Di C23, int arr[100] = {}; juga bisa dipakai. Tanpa inisialisasi sama sekali, isi array lokal adalah nilai sampah yang tidak menentu.