Banyak program perlu menyimpan daftar string: nama hari, daftar kode mata kuliah, daftar menu, atau daftar argumen baris perintah. C tidak memiliki tipe String bawaan—string adalah array char yang diakhiri karakter nol ('\0'). Untuk menyimpan banyak string, kita punya dua pilihan utama: array 2D char strs[N][M] (setiap string menempati buffer tetap) atau array of pointer char *strs[N] (setiap elemen menunjuk ke string yang panjangnya berbeda-beda). Pada chapter ini kita akan membandingkan keduanya dari sisi memori, fleksibilitas, dan keamanan modifikasi.

Dua Model Array of Strings #

Model 1: char strs[N][M] — matriks karakter N baris × M kolom. Setiap baris menyimpan satu string dengan kapasitas maksimum M-1 karakter (1 byte untuk '\0'). Memori yang dipakai = N * M byte, terlepas dari panjang string aktual. String bisa dimodifikasi karena buffer adalah milik array sendiri.

Model 2: char *strs[N] — array berisi N pointer ke char. Setiap elemen adalah pointer yang menunjuk ke string di tempat lain (biasanya string literal yang berada di read-only memory). Memori yang dipakai = N * sizeof(char *) = 8N byte pada 64-bit, plus memori yang dialokasikan untuk string aktual. String yang ditunjuk biasanya tidak boleh dimodifikasi (karena string literal adalah read-only).

Perbandingan Kedua Model #

Contoh berikut mendeklarasikan kedua model dengan data yang sama, mencetak isinya, membandingkan sizeof, lalu memodifikasi isi model 1 (yang aman) dan menunjukkan bahwa model 2 berisi string literal yang tidak boleh dimodifikasi.

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

int main(void) {
    /* Array of strings model 1: array 2D char */
    char nama1[3][10] = {
        "Budi",
        "Siti",
        "Andi"
    };

    /* Array of strings model 2: array of pointers */
    const char *nama2[3] = {
        "Budi",
        "Siti",
        "Andi"
    };

    printf("== char nama1[3][10] ==\n");
    for (int i = 0; i < 3; i++) {
        printf("  %s (len=%zu)\n", nama1[i], strlen(nama1[i]));
    }
    printf("  sizeof(nama1)    = %zu (fixed)\n", sizeof(nama1));
    printf("  sizeof(nama1[0]) = %zu (per baris)\n", sizeof(nama1[0]));

    printf("\n== const char *nama2[3] ==\n");
    for (int i = 0; i < 3; i++) {
        printf("  %s (len=%zu)\n", nama2[i], strlen(nama2[i]));
    }
    printf("  sizeof(nama2)    = %zu (3 pointer)\n", sizeof(nama2));
    printf("  sizeof(nama2[0]) = %zu (satu pointer)\n", sizeof(nama2[0]));

    /* Memodifikasi isi array 2D (bisa, memori lokal) */
    strcpy(nama1[0], "Doni");
    printf("\nSetelah strcpy(nama1[0], \"Doni\"):\n");
    printf("  nama1[0] = %s\n", nama1[0]);

    /* Memodifikasi string literal -> UNDEFINED. JANGAN dilakukan. */
    /* nama2[0][0] = 'X'; */  /* bila di-uncomment: segfault */

    return 0;
}

Output yang Dihasilkan:

text
== char nama1[3][10] ==
  Budi (len=4)
  Siti (len=4)
  Andi (len=4)
  sizeof(nama1)    = 30 (fixed)
  sizeof(nama1[0]) = 10 (per baris)

== const char *nama2[3] ==
  Budi (len=4)
  Siti (len=4)
  Andi (len=4)
  sizeof(nama2)    = 24 (3 pointer)
  sizeof(nama2[0]) = 8 (satu pointer)

Setelah strcpy(nama1[0], "Doni"):
  nama1[0] = Doni

Mari kita telaah baris-baris kode di atas:

  1. Baris 8 (char nama1[3][10] = {"Budi", "Siti", "Andi"};): array 2D berukuran 3 baris × 10 kolom. Setiap baris menampung satu string dengan kapasitas maksimum 9 karakter + 1 byte '\0'. Memori total = 30 byte, terlepas dari panjang aktual string (4 karakter).
  2. Baris 14 (const char *nama2[3] = {...};): array 3 pointer const char *. Setiap pointer menunjuk ke string literal yang berada di read-only memory. Memori total = 3 × 8 = 24 byte, plus memori untuk string aktual (5 byte per literal termasuk '\0').
  3. Baris 25 (sizeof(nama1) = 30): ukuran total array 2D = 3 × 10 = 30 byte.
  4. Baris 26 (sizeof(nama1[0]) = 10): nama1[0] adalah baris pertama yang tipenya char[10], berukuran 10 byte.
  5. Baris 32 (sizeof(nama2) = 24): 3 pointer × 8 byte = 24 byte.
  6. Baris 33 (sizeof(nama2[0]) = 8): nama2[0] bertipe const char *, berukuran 8 byte pada 64-bit.
  7. Baris 36 (strcpy(nama1[0], "Doni");): menyalin string "Doni" ke buffer nama1[0]. Aman karena nama1[0] adalah buffer lokal berkapasitas 10 byte, dan "Doni" hanya 5 byte (termasuk '\0').
  8. Baris 40-41 (/* nama2[0][0] = 'X'; */): komentar yang sengaja tidak diaktifkan. Mengubah string literal adalah undefined behavior—biasanya memicu segmentation fault karena string literal disimpan di read-only memory.

Daftar Hari dengan Model 2D #

Contoh berikut menampilkan penggunaan praktis model 2D char[N][M]: menyimpan daftar nama hari. Setiap string bisa dimodifikasi dengan aman, dan sizeof memberi ukuran total yang terprediksi.

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

int main(void) {
    /* Matriks char: array of fixed-size strings */
    char hari[7][10] = {
        "Senin", "Selasa", "Rabu", "Kamis",
        "Jumat", "Sabtu", "Minggu"
    };

    printf("Daftar hari:\n");
    for (int i = 0; i < 7; i++) {
        printf("  %d: %-8s (len=%zu)\n", i, hari[i], strlen(hari[i]));
    }

    /* Mengubah isi (aman karena buffer lokal) */
    strcpy(hari[0], "Monday");
    printf("Setelah ganti hari[0]: %s\n", hari[0]);

    /* Bandingkan memori yang dipakai */
    printf("sizeof(hari) = %zu byte\n", sizeof(hari));  /* 7 * 10 = 70 */

    return 0;
}

Output yang Dihasilkan:

text
Daftar hari:
  0: Senin    (len=5)
  1: Selasa   (len=6)
  2: Rabu     (len=4)
  3: Kamis    (len=5)
  4: Jumat    (len=5)
  5: Sabtu    (len=5)
  6: Minggu   (len=6)
Setelah ganti hari[0]: Monday
sizeof(hari) = 70 byte

Pada contoh di atas, array hari berukuran 70 byte (7 × 10). Format %-8s mencetak string rata kiri dengan lebar minimal 8 karakter—berguna untuk membuat tabel yang rapi. Pengubahan hari[0] dari "Senin" ke "Monday" aman karena "Monday" hanya 7 byte (termasuk '\0'), masih muat dalam kapasitas 10 byte per baris.