Karena array meluruh menjadi pointer, kita bisa memindahkan pointer dari satu elemen ke elemen lain dengan operasi penjumlahan dan pengurangan — inilah yang disebut pointer arithmetic. Mekanismenya cerdas: pointer tidak bergerak per byte, melainkan per elemen sesuai ukuran tipe sasarannya. Fitur inilah mesin di balik iterasi array klasik C dan di balik iterator pada Standard Library yang akan kita pelajari nanti. Chapter ini menuntaskan tiga operasinya (geser, selisih, bandingkan) serta aturan wilayah yang wajib dihormati.

Bergerak per Elemen, bukan per Byte #

Menambahkan bilangan bulat n ke pointer menghasilkan alamat n elemen setelahnya — bukan n byte setelahnya. Compiler mengalikan n dengan sizeof(tipe) secara otomatis. Untuk int berukuran 4 byte, p + 1 sebenarnya melompat 4 byte di memori, tetapi secara semantik tetap berarti “elemen berikutnya”.

cpp
#include <cstddef>
#include <iostream>

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

    int* p1 = &data[1];  // menunjuk elemen bernilai 20
    int* p2 = &data[4];  // menunjuk elemen bernilai 50

    std::cout << "*p1 : " << *p1 << "\n";
    std::cout << "*p2 : " << *p2 << "\n";

    // Selisih pointer = jumlah elemen di antara keduanya (deterministik)
    std::cout << "Selisih elemen : " << (p2 - p1) << "\n";

    // Jarak dalam byte: sama dengan selisih elemen x sizeof(int)
    std::ptrdiff_t jarakByte =
        reinterpret_cast<const char*>(p2) - reinterpret_cast<const char*>(p1);
    std::cout << "Jarak byte     : " << jarakByte << "\n";

    // Pointer dalam array yang sama bisa dibandingkan
    std::cout << "p1 < p2 : " << (p1 < p2) << "\n";

    // Iterasi dengan pointer; data + 5 adalah posisi one-past-the-end (sah)
    std::cout << "Isi array: ";
    for (int* p = data; p != data + 5; ++p) {
        if (p != data) std::cout << " ";
        std::cout << *p;
    }
    std::cout << "\n";

    return 0;
}

Output yang Dihasilkan:

text
*p1 : 20
*p2 : 50
Selisih elemen : 3
Jarak byte     : 12
p1 < p2 : 1
Isi array: 10 20 30 40 50

Bedah program:

  1. Baris 13-14 (p1, p2): dua pointer menunjuk elemen yang berbeda pada array yang sama. Seluruh operasi selanjutnya dilakukan pada pasangan ini.
  2. Baris 19 (p2 - p1): pengurangan dua pointer menghasilkan jumlah elemen di antara keduanya, bukan byte — hasilnya 3. Hasil ini deterministik dan inilah pola paling aman untuk mengukur jarak pointer tanpa menyentuh alamat mentah. Tipe hasilnya std::ptrdiff_t dari header <cstddef>.
  3. Baris 22-23 (jarakByte): dengan meng-cast kedua pointer ke const char* (pointer ke byte), selisihnya berubah menjadi satuan byte: 12. Angka ini mengonfirmasi mekanisme p + 1 = satu langkah sizeof(int) = 4 byte.
  4. Baris 26 (p1 < p2): pointer ke elemen array yang sama dapat dibandingkan dengan <, <=, >, >=. Hasilnya mengikuti urutan posisi di dalam array: p1 sebelum p2, jadi true.
  5. Baris 29-33 (iterasi pointer): p bergerak dari data sampai menyentuh data + 5. Kondisi p != data + 5 adalah idiom klasik; ++p menggeser satu elemen setiap putaran.

Aturan One-Past-the-End #

Baris data + 5 di program di atas sengaja dipilih karena menyimpan aturan yang halus: alamat satu langkah setelah elemen terakhir (one-past-the-end) boleh dihitung dan boleh dipakai sebagai penanda akhir, tetapi tidak boleh di-dereference. Konvensi ini sama yang dipakai end() pada iterator std::vector — rentang selalu setengah terbuka [begin, end). Penanda one-past-the-end inilah yang membuat loop p != data + 5 berhenti dengan benar tanpa membaca elemen yang tidak ada.

Wilayah yang Diizinkan dan yang Melanggar #

Standar C++ mendefinisikan pointer arithmetic hanya di dalam array tempat pointer itu menunjuk, ditambah posisi one-past-the-end-nya. Melanggar batas ini menghasilkan undefined behavior, dan sayangnya compiler tidak menolaknya saat kompilasi:

cpp Kode bermasalah — JANGAN dijalankan
int data[5] = {10, 20, 30, 40, 50};
int* p = data;

int a = *(p + 5 + 1);   // melampaui one-past-the-end: UB
int* q = p - 1;         // sebelum elemen pertama: UB
int b = *(p + 100);     // jauh di luar array: UB

Tiga baris di atas dapat dikompilasi tanpa keluhan, tetapi saat dijalankan perilakunya tidak terdefinisi — program bisa membaca data asing, berjalan seolah normal, atau berhenti mendadak. Pointer p - 1 khususnya sering mengagetkan karena terlihat “hanya mundur satu langkah”; standar tetap menyatakannya invalid bila p adalah elemen pertama. Selama dua pointer yang dikurangkan berasal dari array yang berbeda, hasilnya juga tidak valid. Kedisiplinan sederhananya: perlakukan array sebagai wilayah bertanda pagar, dan pastikan setiap pointer selalu berada di dalam pagar (atau tepat di tanda pagarnya).

Aritmetika pointer selesai kita kuasai: geser, selisih, bandingkan, plus pagar wilayahnya. Berikutnya kita berkenalan dengan kerabat dekat pointer yang jauh lebih ketat aturannya — reference, alias permanen untuk variabel lain.

Referensi: cppreference — arithmetic operators (pointer) , cppreference — pointer , cppreference — ptrdiff_t .