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.
#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:
Sebelum pemanggilan: x = 10
Di dalam fungsi, a = 999
Setelah pemanggilan: x = 10Mari kita telaah baris-baris kode di atas:
- Baris 4 (
void ubah_nilai(int a)): parameter formalaadalah variabel lokal baru yang dibuat di stack frame fungsiubah_nilai. - Baris 5 (
a = 999;): penugasana = 999mengubah nilai variabel lokaladi stack frame fungsiubah_nilai. Variabelxdi stack framemaintidak tersentuh. - Baris 10 (
ubah_nilai(x);): nilaix(yaitu10) disalin ke parameterapada saat pemanggilan. Setelah fungsi selesai, stack frameubah_nilaidihapus—variabelaikut hilang. - Baris 11 (
printf("Setelah pemanggilan: x = %d\n", x);): nilaixtetap10karena fungsiubah_nilaihanya 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.
#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:
Sebelum: nilai = 5, ptr bukan NULL
Setelah naikkan: nilai = 6
Setelah percobaan NULL: ptr bukan NULL, nilai = 6Pada contoh di atas, ada dua skenario:
- Mode
salah = 0: fungsinaikkan_salahmemodifikasi nilai yang ditunjuk pointerpmelalui(*p)++. Karenapmenunjuk ke alamatnilai(yang dilewatkan olehptr), nilainilaidimainberubah dari5menjadi6. Inilah “pass by reference” yang sering dibicarakan—tetapi secara teknis, alamat pointerpadalah salinan, dan yang berubah adalah memori yang ditunjuk, bukan pointer pemanggil. - Mode
salah = 1: fungsi mencoba mengubahpmenjadiNULL. Karenapadalah salinan dariptr, perubahanp = NULLhanya berlaku di dalam fungsi—ptrdimaintetap menunjuk kenilai. 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.
#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:
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.