Operator address-of (&) adalah pintu gerbang menuju dunia pointer. Tanpa operator ini, kita tidak punya cara untuk memperoleh alamat sebuah variabel dan memberikannya ke pointer. Operator & berperan sebagai jembatan antara dunia variabel bernama dan dunia alamat memori yang menjadi ruang kerja pointer. Pada chapter ini kita akan memahami bagaimana & bekerja, kapan operator ini boleh dipakai, dan kapan tidak—termasuk jebakan klasik memakai & pada ekspresi yang bukan lvalue atau pada variabel yang sudah keluar dari scope.

Cara Kerja Operator & #

Operator & adalah operator unary yang menghasilkan alamat operand. Operand harus berupa lvalue—yaitu ekspresi yang merujuk ke region memori yang bisa ditulis, misalnya nama variabel, elemen array, atau hasil dereference pointer. Operator & sendiri menghasilkan rvalue bertipe T * jika operand bertipe T. Jadi &x bertipe int * bila x bertipe int, dan &arr[i] bertipe int * bila arr bertipe int[].

Mekanisme kerjanya sederhana: saat compiler mengevaluasi &nilai, compiler tidak membaca nilai nilai melainkan mengembalikan alamat memori tempat nilai disimpan. Hasilnya bisa langsung diberikan ke pointer, dicetak dengan %p, atau dilewatkan ke fungsi yang menerima pointer. Penting untuk diingat bahwa & menghasilkan alamat region operand—tidak melakukan dereference apa pun, dan tidak memodifikasi operand.

Contoh berikut memperlihatkan penggunaan & pada dua variabel bertipe berbeda, lalu menyimpan hasilnya pada pointer bertipe yang sesuai.

c
#include <stdio.h>

int main(void) {
    int  nilai = 100;
    long besar = 999999L;

    /* & menghasilkan alamat operand */
    int  *pi = &nilai;
    long *pl = &besar;

    printf("nilai    = %d   di alamat %p\n", nilai, (void*)&nilai);
    printf("besar    = %ld  di alamat %p\n", besar, (void*)&besar);
    printf("pi       = %p (menunjuk nilai)\n", (void*)pi);
    printf("pl       = %p (menunjuk besar)\n", (void*)pl);

    return 0;
}

Output yang Dihasilkan:

text
nilai    = 100   di alamat 0x7ffd3116f7bc
besar    = 999999  di alamat 0x7ffd3116f7b0
pi       = 0x7ffd3116f7bc (menunjuk nilai)
pl       = 0x7ffd3116f7b0 (menunjuk besar)

Alamat yang tersimpan di pi persis sama dengan &nilai—kita baru saja menyalin alamat tersebut dari &nilai ke variabel pi saat inisialisasi. Begitu pula pl dan &besar. Tipe pointer pi adalah int * agar compiler tahu bahwa 4 byte pada alamat tersebut harus ditafsirkan sebagai int; tipe pl adalah long * untuk 8 byte long. Mencoba menyimpan &nilai ke long * akan menimbulkan peringatan -Wincompatible-pointer-types dan biasanya menandakan bug.

Mari kita telaah baris-baris kode di atas:

  1. Baris 4-5 (int nilai = 100; long besar = 999999L;): dua variabel bertipe berbeda. Suffix L menandakan literal long agar kompatibel dengan tipe besar.
  2. Baris 8-9 (int *pi = &nilai; long *pl = &besar;): &nilai menghasilkan int * yang disalin ke pi; &besar menghasilkan long * yang disalin ke pl. Tipe kiri dan kanan harus cocok, jika tidak compiler memperingatkan.
  3. Baris 11-12 (printf("... di alamat %p", (void*)&nilai);): cetak alamat langsung dari &nilai. Cast (void*) diperlukan karena %p mengharapkan void * (ISO/IEC 9899 §7.21.6.1).
  4. Baris 13-14 (printf("pi = %p ...", (void*)pi);): cetak nilai pointer pi, yang merupakan salinan dari &nilai. Karena keduanya menunjuk ke tempat yang sama, outputnya identik dengan baris 11.

& Mengikat Variabel ke Fungsi #

Salah satu kegunaan paling penting & adalah meneruskan alamat variabel lokal ke fungsi lain. Tanpa &, fungsi hanya menerima salinan nilai parameter dan tidak bisa mengubah variabel pemanggil. Dengan &, fungsi menerima alamat variabel dan bisa membaca/menulis memori pemanggil secara langsung.

c
#include <stdio.h>

void tampilkan_alamat(int *param) {
    printf("Di dalam fungsi: alamat param = %p, nilai = %d\n",
           (void*)param, *param);
}

int main(void) {
    int x = 77;

    printf("Di dalam main  : alamat x     = %p, nilai = %d\n",
           (void*)&x, x);

    /* Meneruskan alamat x ke fungsi */
    tampilkan_alamat(&x);
    return 0;
}

Output yang Dihasilkan:

text
Di dalam main  : alamat x     = 0x7fff8b1a05ac, nilai = 77
Di dalam fungsi: alamat param = 0x7fff8b1a05ac, nilai = 77

Alamat x di main dan alamat yang diterima parameter param di fungsi persis sama. Itulah inti dari pass-by-pointer: tidak ada duplikasi nilai variabel x; fungsi bekerja langsung pada memori yang sama. Inilah dasar untuk out-parameter, modifikasi variabel pemanggil, dan efisiensi passing struct/array besar—topik yang akan kita perdalam di chapter Pointer sebagai Parameter.