Banyak bahasa pemrograman membedakan pass by value dan pass by reference secara eksplisit. C tidak—semua argument di C dilewatkan secara pass by value. Yang berarti, setiap argument disalin ke parameter formal saat pemanggilan; modifikasi parameter formal di dalam fungsi tidak memengaruhi variabel pemanggil. Bahkan pointer pun dilewatkan dengan nilai—alamat yang disalin ke parameter pointer adalah salinan dari alamat asli. Pada chapter ini kita akan menelusuri mekanisme pass by value, menunjukkan bahwa pointer juga berlaku pass by value, serta implikasinya pada struct yang besar.

Pass by Value pada Tipe Primitif #

Saat kita memanggil fungsi f(x) di mana x bertipe int, compiler menyalin nilai x ke parameter formal a di fungsi f. Apapun yang dilakukan fungsi f terhadap a, tidak memengaruhi x di pemanggil. Mekanisme ini menjaga encapsulation: pemanggil yakin bahwa variabelnya tidak akan diam-diam diubah oleh fungsi.

Visualisasi Konsep #

Diagram berikut memperlihatkan mekanisme pass by value: nilai argumen pemanggil disalin ke parameter formal, dan dua variabel tersebut menempati region memori yang berbeda:

Pada diagram di atas, pemanggilan ubah_nilai(x) menyalin nilai x (10) ke parameter formal a di stack frame fungsi. Kedua variabel menempati region memori yang berbeda—x di stack frame main, a di stack frame ubah_nilai. Saat fungsi melakukan a = 999, perubahan hanya terjadi pada a (variabel lokal fungsi), sementara x di pemanggil tetap bernilai 10. Setelah fungsi kembali, stack frame fungsi dihapus dan variabel a ikut hilang—pemanggil tidak pernah melihat modifikasi tersebut. Inilah inti pass by value: argumen disalin, modifikasi parameter formal tidak memengaruhi variabel pemanggil.

c
#include <stdio.h>

/* Pass by value: perubahan a tidak memengaruhi x di pemanggil */
void ubah_nilai(int a) {
    a = 999;                 /* Modifikasi lokal - tidak terlihat di main */
    printf("Di dalam fungsi, a = %d\n", a);
}

int main(void) {
    int x = 10;
    printf("Sebelum pemanggilan: x = %d\n", x);
    ubah_nilai(x);
    printf("Setelah pemanggilan: x = %d\n", x);
    return 0;
}

Output yang Dihasilkan:

text
Sebelum pemanggilan: x = 10
Di dalam fungsi, a = 999
Setelah pemanggilan: x = 10

Mari kita telaah baris-baris kode di atas:

  1. Baris 4 (void ubah_nilai(int a)): parameter formal a adalah variabel lokal baru yang dibuat di stack frame fungsi ubah_nilai.
  2. Baris 5 (a = 999;): penugasan a = 999 mengubah nilai variabel lokal a di stack frame fungsi ubah_nilai. Variabel x di stack frame main tidak tersentuh.
  3. Baris 10 (ubah_nilai(x);): nilai x (yaitu 10) disalin ke parameter a pada saat pemanggilan. Setelah fungsi selesai, stack frame ubah_nilai dihapus—variabel a ikut hilang.
  4. Baris 11 (printf("Setelah pemanggilan: x = %d\n", x);): nilai x tetap 10 karena fungsi ubah_nilai hanya memodifikasi salinan lokal.

Pointer Juga Pass by Value #

Salah satu miskonsepsi paling umum di C adalah menganggap pointer secara otomatis memberikan pass by reference. Faktanya, pointer juga dilewatkan dengan nilai—alamat yang disimpan di pointer disalin ke parameter formal. Yang membuat pointer istimewa adalah: lewat pointer, fungsi dapat mengakses dan memodifikasi memori yang ditunjuk. Tetapi mengubah pointer itu sendiri (mengubah alamat yang ditunjuk) tidak memengaruhi pointer pemanggil.

c
#include <stdio.h>

/* Bahkan pointer pun pass-by-value: alamat pointer yang disalin */
void naikkan_salah(int *p, int salah) {
    if (salah) {
        p = NULL;            /* Mengubah salinan pointer - tidak mengubah ptr di main */
        return;
    }
    (*p)++;                  /* OK: mengubah nilai yang ditunjuk */
}

int main(void) {
    int nilai = 5;
    int *ptr = &nilai;
    printf("Sebelum: nilai = %d, ptr %s NULL\n", nilai, ptr == NULL ? "adalah" : "bukan");

    naikkan_salah(ptr, 0);   /* Mode: naikkan nilai yang ditunjuk */
    printf("Setelah naikkan: nilai = %d\n", nilai);

    naikkan_salah(ptr, 1);   /* Mode: coba NULL-kan ptr dari dalam fungsi */
    printf("Setelah percobaan NULL: ptr %s NULL, nilai = %d\n",
           ptr == NULL ? "adalah" : "bukan", nilai);
    return 0;
}

Output yang Dihasilkan:

text
Sebelum: nilai = 5, ptr bukan NULL
Setelah naikkan: nilai = 6
Setelah percobaan NULL: ptr bukan NULL, nilai = 6

Pada contoh di atas, ada dua skenario:

  • Mode salah = 0: fungsi naikkan_salah memodifikasi nilai yang ditunjuk pointer p melalui (*p)++. Karena p menunjuk ke alamat nilai (yang dilewatkan oleh ptr), nilai nilai di main berubah dari 5 menjadi 6. Inilah “pass by reference” yang sering dibicarakan—tetapi secara teknis, alamat pointer p adalah salinan, dan yang berubah adalah memori yang ditunjuk, bukan pointer pemanggil.
  • Mode salah = 1: fungsi mencoba mengubah p menjadi NULL. Karena p adalah salinan dari ptr, perubahan p = NULL hanya berlaku di dalam fungsi—ptr di main tetap menunjuk ke nilai. Inilah bukti bahwa pointer dilewatkan dengan nilai.

Pass by Value pada Struct #

Saat argument berupa struct, seluruh struct disalin ke parameter formal. Untuk struct kecil (beberapa int), overhead salinan tidak signifikan. Tetapi untuk struct besar (ratusan byte atau berisi array besar), salinan dapat membebani stack dan memperlambat pemanggilan. Inilah alasan umum untuk melewatkan struct via pointer (const Titik *p alih-alih Titik p) bila struct cukup besar.

c
#include <stdio.h>

/* Pass by value pada struct - seluruh struct disalin */
typedef struct {
    int x;
    int y;
} Titik;

void geser(Titik t) {
    t.x += 10;                /* Mengubah salinan, bukan titik asli */
    t.y += 10;
    printf("Di dalam fungsi: Titik(%d, %d)\n", t.x, t.y);
}

int main(void) {
    Titik p = {3, 4};
    printf("Sebelum: Titik(%d, %d)\n", p.x, p.y);
    geser(p);
    printf("Setelah: Titik(%d, %d)\n", p.x, p.y);
    return 0;
}

Output yang Dihasilkan:

text
Sebelum: Titik(3, 4)
Di dalam fungsi: Titik(13, 14)
Setelah: Titik(3, 4)

Pada contoh di atas, struct Titik berisi dua int. Pemanggilan geser(p) menyalin seluruh struct p ke parameter formal t. Modifikasi t.x += 10 dan t.y += 10 hanya berlaku di salinan; struct p di main tetap Titik(3, 4).

Pass by value adalah pondasi keamanan C: pemanggil yakin bahwa variabelnya tidak akan diubah kecuali ia secara eksplisit melewatkan alamatnya. Untuk benar-benar memodifikasi variabel pemanggil, kita perlu menggunakan pointer parameters—topik chapter berikutnya.