Pointer arithmetic adalah salah satu kemampuan paling khas C yang membuat bahasa ini begitu ekspresif untuk operasi level memori. Berbeda dengan aritmetika integer biasa, menambahkan 1 pada sebuah pointer tidak selalu menambahkan 1 byte ke alamat—melainkan menambahkan sizeof(tipe_yang_ditunjuk) byte. Artinya int *p; p + 1 maju 4 byte, sedangkan char *c; c + 1 maju hanya 1 byte. Inilah mengapa kita bisa menelusuri elemen array dengan pointer tanpa peduli ukuran elemen. Pada chapter ini kita akan membandingkan aritmetika pada int* dan char*, serta mempraktikkan iterasi array dengan pointer sebagai alternatif loop berbasis indeks.

Cara Kerja Pointer Arithmetic #

Pada tipe pointer T *p, ekspresi p + n menghasilkan pointer baru yang menunjuk ke region memori n * sizeof(T) byte setelah p. Kebalikannya, p - n menghasilkan pointer n * sizeof(T) byte sebelum p. Dua pointer dengan tipe yang sama dapat dikurangkan (p2 - p1) untuk memperoleh jumlah elemen di antara keduanya—bukan jumlah byte. Standar C (ISO/IEC 9899 §6.5.6) juga mengizinkan perbandingan pointer dengan <, >, <=, >=, ==, != selama kedua pointer menunjuk ke elemen dalam array yang sama atau satu-past-the-end.

Mekanisme di balik layar: saat compiler melihat pi + 1 dengan pi bertipe int *, compiler menerjemahkan ekspresi ini menjadi (int*)((char*)pi + 1 * sizeof(int))—yaitu alamat pi digeser maju sizeof(int) byte. Komputasi ini gratis bagi programmer: compiler mengalikan increment dengan ukuran elemen secara otomatis. Jika kita ingin maju 1 byte saja, kita harus cast pointer ke char * terlebih dahulu—seperti yang akan kita lihat pada contoh.

Visualisasi Konsep #

Diagram berikut memperlihatkan bagaimana pointer arithmetic bekerja secara berbeda untuk int* (4 byte per elemen) dan char* (1 byte per elemen):

Pada diagram di atas, array int melompat 4 byte setiap kali pointer bertambah 1, sedangkan array char melompat hanya 1 byte. Compiler secara otomatis mengalikan offset dengan sizeof(T) sesuai tipe pointer—programmer tidak perlu menyebut ukuran eksplisit. Inilah inti pointer arithmetic: tipe pointer membawa informasi ukuran elemen, sehingga pi + 1 dan pc + 1 menghasilkan lompatan alamat yang berbeda meskipun ditulis dengan sintaks yang sama.

c
#include <stdio.h>

int main(void) {
    int data[5] = {10, 20, 30, 40, 50};
    int *pi = data;

    printf("Alamat awal pi      = %p\n", (void*)pi);
    printf("Alamat pi + 1       = %p  (selisih %ld byte)\n",
           (void*)(pi + 1),
           (long)((char*)(pi + 1) - (char*)pi));
    printf("Alamat pi + 2       = %p  (selisih %ld byte)\n",
           (void*)(pi + 2),
           (long)((char*)(pi + 2) - (char*)pi));
    printf("Nilai *(pi + 3)     = %d\n", *(pi + 3));

    char teks[] = "ABCDE";
    char *pc = teks;
    printf("\nAlamat awal pc      = %p\n", (void*)pc);
    printf("Alamat pc + 1       = %p  (selisih %ld byte)\n",
           (void*)(pc + 1),
           (long)((pc + 1) - pc));
    printf("Nilai *(pc + 2)     = %c\n", *(pc + 2));

    return 0;
}

Output yang Dihasilkan:

text
Alamat awal pi      = 0x7ffffa105550
Alamat pi + 1       = 0x7ffffa105554  (selisih 4 byte)
Alamat pi + 2       = 0x7ffffa105558  (selisih 8 byte)
Nilai *(pi + 3)     = 40

Alamat awal pc      = 0x7ffffa10554a
Alamat pc + 1       = 0x7ffffa10554b  (selisih 1 byte)
Nilai *(pc + 2)     = C

Pada int *pi, ekspresi pi + 1 maju 4 byte (dari ...550 ke ...554); pi + 2 maju 8 byte (ke ...558). Sebaliknya, pada char *pc, ekspresi pc + 1 maju hanya 1 byte (dari ...54a ke ...54b). Compiler otomatis mengalikan offset dengan sizeof(T) sesuai tipe pointer—maka programmer tidak perlu menyebut ukuran secara eksplisit. Inilah inti pointer arithmetic: tipe pointer membawa informasi ukuran elemen.

Mari kita telaah baris-baris kode di atas:

  1. Baris 4-5 (int data[5] = ...; int *pi = data;): array data berisi 5 int yang menempati alamat berurutan; pi menunjuk ke elemen pertama. Nama array data otomatis decay menjadi int *.
  2. Baris 7-12 (printf("... pi + 1 ...")): cetak alamat pi, pi + 1, dan pi + 2. Selisih byte dihitung dengan cast ke char * agar selisih byte (bukan elemen) yang ditampilkan: (char*)(pi + 1) - (char*)pi menghasilkan 4 (ukuran int).
  3. Baris 13 (printf("Nilai *(pi + 3) = %d", *(pi + 3));): pointer arithmetic + dereference. pi + 3 menunjuk ke elemen keempat; dereference membaca nilai int di alamat tersebut, yaitu 40.
  4. Baris 15-16 (char teks[] = "ABCDE"; char *pc = teks;): string literal diletakkan ke array char yang bisa dimodifikasi; pc menunjuk ke huruf pertama 'A'.
  5. Baris 19 (printf("... pc + 1 ... selisih %ld", (long)((pc + 1) - pc));): di sini pc + 1 dan pc keduanya bertipe char *, sehingga selisihnya 1 elemen (= 1 byte) tanpa perlu cast. Hasilnya 1 byte, sesuai sizeof(char).
  6. Baris 21 (*(pc + 2)): maju 2 byte dari pc (yang menunjuk 'A'), lalu dereference. Hasilnya 'C'—huruf ketiga.

Iterasi Array dengan Pointer #

Pointer arithmetic memungkinkan iterasi array tanpa variabel indeks sama sekali. Kita pakai dua pointer: awal yang menunjuk ke elemen pertama, dan akhir yang menunjuk ke posisi one-past-the-end (satu elemen setelah elemen terakhir). Standar C mengizinkan pembentukan pointer one-past-the-end secara khusus untuk keperluan ini, tetapi pointer tersebut tidak boleh di-dereference.

c
#include <stdio.h>

int main(void) {
    int arr[6] = {5, 10, 15, 20, 25, 30};
    int *awal  = arr;
    int *akhir  = arr + 6;     /* satu-past-the-end */

    printf("Selisih pointer (akhir - awal) = %ld elemen\n",
           (long)(akhir - awal));

    /* Iterasi dengan pointer, bukan indeks */
    int *p = awal;
    int i = 0;
    while (p != akhir) {
        printf("  [%d] alamat %p -> %d\n", i, (void*)p, *p);
        p++;
        i++;
    }

    /* Pointer bisa juga dibandingkan dengan < > <= >= */
    int *tengah = arr + 3;
    printf("\ntengah di antara awal dan akhir? %s\n",
           (awal < tengah && tengah < akhir) ? "ya" : "tidak");

    return 0;
}

Output yang Dihasilkan:

text
Selisih pointer (akhir - awal) = 6 elemen
  [0] alamat 0x7ffe810f9780 -> 5
  [1] alamat 0x7ffe810f9784 -> 10
  [2] alamat 0x7ffe810f9788 -> 15
  [3] alamat 0x7ffe810f978c -> 20
  [4] alamat 0x7ffe810f9790 -> 25
  [5] alamat 0x7ffe810f9794 -> 30

tengah di antara awal dan akhir? ya

Ekspresi akhir - awal menghasilkan 6—jumlah elemen, bukan jumlah byte (yang seharusnya 24). Loop while (p != akhir) berhenti tepat saat p mencapai posisi one-past-the-end. Pola ini begitu umum di C dan menjadi dasar idiom iterator di C++ (yang menggeneralisasi konsep ini). Perbandingan awal < tengah && tengah < akhir valid karena ketiga pointer menunjuk ke elemen dalam array yang sama.