Menu

Array di C: Deklarasi, Inisialisasi, dan Perulangan

Cara mendeklarasikan dan menginisialisasi array di C, mengakses indeks mulai dari nol, menghitung panjangnya dengan sizeof, menelusuri elemennya dengan perulangan, dan kenapa membaca melewati ujung array adalah undefined behavior, bukan pesan error.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

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 -O0 dan -O2.

"Terlihat berjalan normal" adalah yang paling berbahaya, karena artinya bug itu ikut dirilis. Tiga cara bertahan:

  1. Gunakan i < n di perulangan. Sebagian besar akses yang kebablasan adalah kesalahan off-by-one di kondisi perulangan.
  2. Validasi indeks yang berasal dari input.
  1. Build dengan sanitizer selama pengembangan. gcc -fsanitize=address -g prog.c mengubah 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.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai