Array di C bukan sekadar kumpulan nilai—array adalah blok memori yang menempati alamat berurutan. Memahami bagaimana compiler menyusun array di memori, bagaimana pointer arithmetic bekerja, dan bagaimana padding memengaruhi tipe compound, akan membantu kita menulis kode yang efisien dan menghindari bug halus. Pada chapter ini kita akan menelusuri layout memori array, hubungan antara arr[i] dan *(arr + i), serta konsep alignment dan padding pada struct yang menyimpan array.

Layout Memori Array #

Saat kita mendeklarasikan int arr[5], compiler memesan satu blok memori sebesar 5 * sizeof(int) = 20 byte (pada sistem 64-bit dengan sizeof(int) = 4). Alamat elemen pertama arr[0] adalah alamat awal blok; elemen kedua arr[1] berada tepat 4 byte setelahnya, dan seterusnya. Karena elemen berurutan, kita bisa mengakses elemen mana pun dengan aritmetika pointer.

Alamat Elemen dan Pointer Arithmetic #

Contoh berikut mencetak alamat setiap elemen array, memperlihatkan jaraknya yang seragam, lalu mengakses elemen melalui pointer arithmetic.

c
#include <stdio.h>

int main(void) {
    int arr[5] = {100, 200, 300, 400, 500};

    /* Cetak alamat setiap elemen */
    printf("Ukuran int: %zu byte\n", sizeof(int));
    printf("Alamat tiap elemen:\n");
    for (int i = 0; i < 5; i++) {
        printf("  &arr[%d] = %p  (jarak dari awal = %td)\n",
               i, (void *)&arr[i], (char *)&arr[i] - (char *)&arr[0]);
    }

    /* Akses elemen via pointer aritmetika */
    int *p = arr;
    printf("\nAkses via pointer:\n");
    for (int i = 0; i < 5; i++) {
        printf("  *(p + %d) = %d\n", i, *(p + i));
    }

    /* Demonstrasi alignment: struct layout */
    struct S {
        char c;
        int  x;
    };
    printf("\nsizeof(char) = %zu\n", sizeof(char));
    printf("sizeof(int)  = %zu\n", sizeof(int));
    printf("sizeof(struct S { char; int; }) = %zu (ada padding)\n", sizeof(struct S));

    return 0;
}

Output yang Dihasilkan:

text
Ukuran int: 4 byte
Alamat tiap elemen:
  &arr[0] = 0x7ffc562c6650  (jarak dari awal = 0)
  &arr[1] = 0x7ffc562c6654  (jarak dari awal = 4)
  &arr[2] = 0x7ffc562c6658  (jarak dari awal = 8)
  &arr[3] = 0x7ffc562c665c  (jarak dari awal = 12)
  &arr[4] = 0x7ffc562c6660  (jarak dari awal = 16)

Akses via pointer:
  *(p + 0) = 100
  *(p + 1) = 200
  *(p + 2) = 300
  *(p + 3) = 400
  *(p + 4) = 500

sizeof(char) = 1
sizeof(int)  = 4
sizeof(struct S { char; int; }) = 8 (ada padding)

Mari kita telaah baris-baris kode di atas:

  1. Baris 4 (int arr[5] = {...}): array 5 elemen int. Total memori = 5 × 4 = 20 byte.
  2. Baris 7 (printf("Ukuran int: %zu byte\n", sizeof(int));): mencetak sizeof(int) = 4 byte—menjadi dasar perhitungan offset alamat.
  3. Baris 10 (printf(" &arr[%d] = %p (jarak dari awal = %td)\n", i, (void *)&arr[i], (char *)&arr[i] - (char *)&arr[0]);): (void *)&arr[i] mencetak alamat memori elemen ke-i dalam format heksadesimal pointer. Cast ke (char *) memungkinkan pengurangan alamat menghasilkan offset dalam byte (bukan dalam unit elemen).
  4. Baris 14 (int *p = arr;): pointer p menunjuk ke elemen pertama array. Ekspresi arr di sini meluruh menjadi pointer ke arr[0]—sama dengan &arr[0].
  5. Baris 18 (*(p + i)): pointer arithmetic. p + i menambah alamat sebanyak i * sizeof(int) byte, lalu * dereference alamat tersebut untuk membaca nilai. Hasilnya sama dengan arr[i].
  6. Baris 22-26 (struct S { char c; int x; };): struct dengan dua field. Walaupun sizeof(char) = 1 dan sizeof(int) = 4, total sizeof(struct S) = 8—bukan 5. Selisih 3 byte adalah padding yang ditambahkan compiler agar int x mulai pada alamat yang kelipatan 4 (alignment).

Pointer Arithmetic: arr[i] Ekuivalen dengan *(arr + i) #

Karena array meluruh menjadi pointer dan aritmetika pointer bekerja dengan satuan elemen, ekspresi arr[i] dan *(arr + i) benar-benar identik di C. Bahkan lebih—ekspresi i[arr] juga valid (komutatif), walaupun tidak lazim dipakai. Contoh berikut membuktikan kesetaraan ini.

c
#include <stdio.h>

int main(void) {
    int a[4] = {10, 20, 30, 40};

    /* arr[i] identik dengan *(arr + i) */
    int *p = a;
    printf("a[0]    = %d, *p        = %d\n", a[0], *p);
    printf("a[2]    = %d, *(p + 2)  = %d\n", a[2], *(p + 2));

    /* a + 1 menambah sizeof(int) byte, bukan 1 byte */
    printf("p     = %p\n", (void *)p);
    printf("p + 1 = %p (naik %zu byte)\n", (void *)(p + 1), sizeof(int));

    /* Demonstrasi arr[i] == i[arr] (sintaks C合法 tapi jangan dipakai) */
    printf("a[3] = %d, 3[a] = %d\n", a[3], 3[a]);

    return 0;
}

Output yang Dihasilkan:

text
a[0]    = 10, *p        = 10
a[2]    = 30, *(p + 2)  = 30
p     = 0x7ffd05009850
p + 1 = 0x7ffd05009854 (naik 4 byte)
a[3] = 40, 3[a] = 40

Pada contoh di atas, alamat p + 1 naik 4 byte dari p (bukan 1 byte), karena aritmetika pointer int * bergerak dalam satuan sizeof(int). Hasil 3[a] = a[3] = 40—kedua ekspresi diterjemahkan menjadi *(a + 3). Meskipun valid, menulis 3[a] membingungkan pembaca; selalu gunakan a[3] untuk kejelasan.