Dua deklarasi char s[] = "mutable"; dan char *p = "immutable"; terlihat mirip secara sintaksis: keduanya menghasilkan variabel yang bisa dicetak dengan printf("%s", ...) dan bisa diiterasi karakter per karakter. Namun di balik kemiripan itu, keduanya menyimpan string di lokasi memori yang sangat berbeda dengan sifat mutabilitas yang berlawanan. Array char menyalin byte literal ke stack sehingga setiap byte bisa diubah; pointer char * ke literal hanya menyimpan alamat ke memori read-only yang tidak boleh diubah. Perbedaan ini adalah sumber bug klasik di kalangan pemula C. Pada chapter ini kita akan membandingkan keduanya secara langsung, menunjukkan perbedaan sizeof, dan mendemonstrasikan apa yang boleh serta tidak boleh dilakukan pada masing-masing pola.

Perbandingan Langsung: Array vs Pointer #

Mari kita taruh kedua deklarasi berdampingan dan amati perbedaan perilakunya. Kita akan melihat sizeof, strlen, serta upaya mutasi.

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

/* Perbandingan char array vs char pointer */
int main(void) {
    char arr[] = "mutable";      /* array di stack */
    const char *ptr = "immutable";  /* pointer ke literal */

    /* sizeof berbeda */
    printf("sizeof arr = %zu\n", sizeof arr);
    printf("sizeof ptr = %zu\n", sizeof ptr);

    /* strlen sama-sama menghitung karakter */
    printf("strlen arr = %zu\n", strlen(arr));
    printf("strlen ptr = %zu\n", strlen(ptr));

    /* Array bisa dimutasi */
    arr[0] = 'M';
    printf("arr setelah mutasi: %s\n", arr);

    /* Pointer literal TIDAK boleh dimutasi */
    /* ptr[0] = 'I'; */  /* UB: segfault */
    printf("ptr tetap: %s\n", ptr);

    /* Array tidak bisa di-assign ke alamat lain */
    /* arr = "teks lain"; */  /* compile error */

    /* Pointer bisa di-assign ke literal lain */
    ptr = "literal lain";
    printf("ptr diarahkan ulang: %s\n", ptr);
    return 0;
}

Output yang Dihasilkan:

text
sizeof arr = 8
sizeof ptr = 8
strlen arr = 7
strlen ptr = 9
arr setelah mutasi: Mutable
ptr tetap: immutable
ptr diarahkan ulang: literal lain

Mari kita telaah baris-baris kode di atas:

  1. Baris 7 (char arr[] = "mutable";): compiler mengalokasikan array 8 byte (7 karakter + null terminator) di stack dan menyalin byte literal ke array tersebut. Array ini menjadi salinan independen yang dapat dimutasi.
  2. Baris 8 (const char *ptr = "immutable";): compiler menempatkan literal "immutable" (10 byte) di read-only data segment. Variabel ptr hanyalah pointer (8 byte pada x86-64) yang menyimpan alamat byte pertama literal tersebut.
  3. Baris 11-12 (sizeof arr dan sizeof ptr): sizeof arr = 8 (ukuran array: 7 karakter + null terminator). sizeof ptr = 8 (ukuran pointer pada x86-64). Tampak sama secara numerik di sini, tetapi maknanya sangat berbeda—sizeof arr mencerminkan ukuran array; sizeof ptr hanya mencerminkan ukuran pointer, bukan string yang ditunjuk.
  4. Baris 15-16 (strlen arr dan strlen ptr): strlen menghitung karakter sebelum null terminator, sehingga 7 untuk "mutable" dan 9 untuk "immutable".
  5. Baris 19 (arr[0] = 'M';): mutasi array sah karena array berada di stack yang dapat ditulis. Hasil: "Mutable".
  6. Baris 23 (komentar ptr[0] = 'I';): baris ini dikomentari karena akan menimbulkan UB—literal berada di memori read-only. Compiler tidak memberi peringatan karena const char * sudah memberi sinyal bahwa kita berkomitmen untuk tidak memutasi.
  7. Baris 27 (ptr = "literal lain";): pointer dapat diarahkan ulang ke literal lain. Pointer hanyalah variabel yang menyimpan alamat—mengubah alamat yang disimpan adalah operasi yang sah.

Pointer ke Dalam Array #

Salah satu kegunaan pointer char * yang sah adalah menunjuk ke dalam array char yang sudah ada. Karena array berada di stack (memori yang dapat ditulis), pointer ini dapat dipakai untuk membaca dan memutasi byte array.

c
#include <stdio.h>

/* Pointer yang menunjuk ke dalam array */
int main(void) {
    char s[] = "Belajar C";
    char *p = s;   /* p menunjuk ke s[0] */

    printf("Awal: p = %s\n", p);

    /* Geser pointer maju tiga byte */
    p = p + 3;
    printf("Setelah p+3: p = %s\n", p);

    /* Modifikasi karakter ke-4 dari 'a' menjadi 'A' lewat pointer */
    *p = 'A';
    printf("Setelah modifikasi: s = %s\n", s);

    /* Pointer ke literal: hanya bisa baca */
    const char *q = "literal";
    /* q[0] = 'L'; */   /* UB */
    printf("q = %s\n", q);
    return 0;
}

Output yang Dihasilkan:

text
Awal: p = Belajar C
Setelah p+3: p = ajar C
Setelah modifikasi: s = BelAjar C
q = literal

Mari kita telaah baris-baris kode di atas:

  1. Baris 6 (char s[] = "Belajar C";): array 10 byte di stack berisi karakter B, e, l, a, j, a, r, , C, '\0'.
  2. Baris 7 (char *p = s;): variabel pointer p menunjuk ke s[0]. Karena s adalah array yang dapat dimutasi, p juga dapat dipakai untuk memutasi byte array.
  3. Baris 11 (p = p + 3;): aritmetika pointer—p sekarang menunjuk ke s[3] (karakter 'a'). Karakter yang dicetak oleh printf("%s", p) dimulai dari posisi ini: "ajar C".
  4. Baris 15 (*p = 'A';): kita menulis karakter 'A' ke alamat yang ditunjuk p, yaitu s[3]. Karena s[3] sebelumnya berisi 'a', sekarang berisi 'A'. Mutasi melalui pointer sah karena pointer menunjuk ke array (memori stack yang dapat ditulis).
  5. Baris 16 (printf("Setelah modifikasi: s = %s\n", s);): kita mencetak array s utuh dan melihat bahwa karakter ke-4 berubah dari 'a' menjadi 'A', menghasilkan "BelAjar C".
  6. Baris 19 (const char *q = "literal";): pointer q menunjuk ke literal di read-only memory. Tidak boleh dimutasi.
  7. Baris 20 (komentar q[0] = 'L';): baris ini dikomentari karena akan menimbulkan UB. Berbeda dengan p di atas yang menunjuk ke array, q menunjuk ke memori read-only.

Ringkasan Konsep #

char s[] dan char *p tampak serupa tetapi berperilaku berbeda secara fundamental. Array char[] menyalin byte literal ke stack, menghasilkan objek yang dapat dimutasi dan yang ukurannya dilaporkan sizeof. Pointer char * (atau lebih baik const char *) ke literal hanya menyimpan alamat ke memori read-only—byte di alamat tersebut tidak boleh dimutasi. sizeof pada pointer hanya mengembalikan ukuran pointer (8 byte pada x86-64), bukan panjang string. Pointer dapat diarahkan ulang ke literal lain; array tidak dapat di-assign ke alamat baru. Pointer juga dapat dipakai untuk menunjuk ke dalam array yang sudah ada—dalam kasus ini, mutasi melalui pointer sah karena array berada di memori yang dapat ditulis. Pemahaman perbedaan ini adalah kunci untuk menghindari bug klasik segmentation fault saat mencoba memutasi string literal, dan menjadi fondasi sebelum kita masuk ke chapter-chapter tentang fungsi string library seperti strlen, strcpy, dan strcat.