Bahasa C selalu meneruskan argumen berupa salinan nilai, tetapi salinan itu boleh berupa alamat. Teknik mengoper alamat variabel agar fungsi dapat mengubah isi variabel pemanggil dikenal sebagai pass by reference. Topik ini membedah mekanismenya melalui fungsi tukar yang klasik, menunjukkan bahwa satu fungsi dapat menghasilkan lebih dari satu nilai lewat parameter pointer, dan merangkum kapan teknik ini layak dipilih.
Fungsi tukar yang benar-benar menukar#
Pada topik pass by value, versi tukar yang menerima dua int hanya menukar salinan, sehingga variabel pemanggil tidak berubah. Perbaikannya mengubah parameter menjadi int *, lalu pemanggil mengirim alamat dengan operator &. Di dalam fungsi, dereference *a dan *b menjangkau variabel asli pemanggil, sehingga pertukaran terjadi pada tempatnya.
#include <stdio.h>
void tukar(int *a, int *b)
{
int sementara = *a;
*a = *b;
*b = sementara;
}
int main(void)
{
int x = 3;
int y = 8;
printf("Sebelum: x = %d, y = %d\n", x, y);
tukar(&x, &y);
printf("Sesudah: x = %d, y = %d\n", x, y);
return 0;
}Sebelum: x = 3, y = 8
Sesudah: x = 8, y = 3Kali ini nilai kedua variabel benar-benar bertukar. Kunci perubahannya hanya dua, yaitu tanda bintang pada deklarasi parameter dan tanda & pada argumen. Contoh scanf("%d", &n) yang digunakan sejak topik I/O dasar sebenarnya menerapkan teknik yang sama, yaitu fungsi menerima alamat n agar dapat menuliskan hasil bacaannya ke variabel pemanggil.
Tanda tangan void tukar(int *a, int *b) juga menyimpan kontrak yang tidak tertulis namun penting. Fungsi berjanji tidak menampung alamat tersebut lebih lama dari panggilannya, dan pemanggil berjanji mengirim alamat variabel yang masih hidup. Dereference terhadap alamat yang telah kedaluwarsa merupakan salah satu bug yang dibahas pada topik Bug Pointer. Selama kedua janji ditepati, pola pass by reference sama amannya dengan pemanggilan fungsi biasa.
Apa yang terjadi di memori#
Diagram berikut menelusuri keadaan saat tukar(&x, &y) sedang berjalan. Parameter a dan b adalah variabel lokal milik fungsi, tetapi isinya berupa salinan alamat &x dan &y, sehingga dereference terhadap keduanya mendarat tepat pada variabel milik main.
Masa hidup a dan b berakhir ketika fungsi kembali, tetapi perubahan pada x dan y tetap ada, karena yang diubah bukan parameter fungsi, melainkan sel yang ditunjuk oleh keduanya. Perbedaan ini menjelaskan mengapa versi pass by value gagal, yaitu fungsi hanya mengubah salinan argumennya sendiri. Teknik ini juga menjadi dasar bagi parameter struct berukuran besar, yang lebih murah dikirim sebagai alamat daripada disalin seluruhnya.
Perhatikan pula bahwa parameter a dan b sendiri tidak pernah ditukar, yang bertukar hanyalah isi sel yang ditunjuk oleh keduanya. Penukaran pada parameter, misalnya dimulai dengan int *sementara = a;, hanya memindahkan dua alamat milik fungsi, dan pemanggil tidak akan melihat akibat apa pun. Pembedaan antara mengubah isi yang ditunjuk dan mengubah alamat yang ditampung menjadi inti dari pola pass by reference, dan pembedaan yang sama berlaku pada topik pointer ke pointer.
Lebih dari satu nilai kembali#
Pernyataan return hanya menyerahkan satu nilai, sedangkan parameter pointer tidak terbatas jumlahnya. Fungsi yang perlu menghasilkan beberapa nilai sekaligus dapat menuliskan hasil tambahannya ke alamat yang disiapkan pemanggil, sebuah pola yang dikenal sebagai parameter keluaran. Program berikut membagi dua bilangan sekaligus menghitung sisa pembagiannya.
#include <stdio.h>
void bagi_sisa(int terbagi, int pembagi, int *hasil_bagi, int *hasil_sisa)
{
*hasil_bagi = terbagi / pembagi;
*hasil_sisa = terbagi % pembagi;
}
int main(void)
{
int bagi = 0;
int sisa = 0;
bagi_sisa(47, 5, &bagi, &sisa);
printf("47 dibagi 5 -> hasil bagi %d, sisa %d\n", bagi, sisa);
bagi_sisa(128, 10, &bagi, &sisa);
printf("128 dibagi 10 -> hasil bagi %d, sisa %d\n", bagi, sisa);
return 0;
}47 dibagi 5 -> hasil bagi 9, sisa 2
128 dibagi 10 -> hasil bagi 12, sisa 8Pemanggil mendeklarasikan bagi dan sisa terlebih dahulu, lalu mengirim alamatnya ke fungsi. Setelah pemanggilan, kedua variabel sudah terisi tanpa perlu dua pemanggilan fungsi terpisah. Pola ini banyak digunakan pustaka standar, misalnya fungsi konversi strtol yang melaporkan posisi berhenti pembacaan melalui parameter pointer semacam ini.
Parameter keluaran sebaiknya didokumentasikan dengan jelas, karena pemanggil perlu tahu parameter mana yang berperan sebagai masukan dan mana yang berperan sebagai wadah hasil. Apabila fungsi dapat gagal di tengah jalan, wadah hasil sebaiknya diisi nilai default sebelum pemanggilan, sehingga pemanggil tidak membaca sisa data yang lama. Untuk hasil alokasi memori yang masa hidupnya dikelola pemanggil, pola yang lebih lengkap dibahas pada modul Memori Dinamis.
Kapan memilih pass by reference#
Teknik ini layak digunakan ketika fungsi memang harus mengubah keadaan pemanggil. Kebutuhan lain muncul ketika lebih dari satu nilai harus dikembalikan, atau ketika data yang dioper berukuran besar sehingga penyalinannya mahal. Untuk data besar yang hanya dibaca, gabungkan dengan const agar fungsi tetap tidak dapat mengubahnya, sebuah kombinasi yang dibahas khusus pada topik const bersama pointer. Untuk nilai kecil seperti int dan double yang tidak perlu diubah, penerusan biasa tetap menjadi pilihan yang lebih sederhana dan lebih mudah dibaca.
// tidak-dijalankan
int x = 3, y = 8;
/* tukar(x, y); ditolak kompilator: tukar meminta int *, bukan int */
tukar(&x, &y); /* bentuk yang benar: kirim alamatnya */Kesalahan umum#
- Lupa menulis
&pada argumen. Fungsi memintaint *sedangkanxbertipeint, sehingga pemanggilantukar(x, y)gagal dikompilasi dengan pesan ketidakcocokan tipe. Kirim&xdan&ysesuai tanda tangan fungsinya. - Mengubah alamat di dalam fungsi alih-alih isinya. Pernyataan
a = bdi dalamtukarhanya memindahkan salinan alamat milik parameter lokal dan tidak menyentuh pemanggil sama sekali. Tulis*a = *bbila yang ingin ditukar adalah nilainya. - Menderereference parameter pointer tanpa jaminan isinya. Fungsi yang menerima
int *tidak tahu apakah pemanggil mengirim alamat yang sah atau NULL, sehingga dereference buta berisiko menghentikan program. Periksa terhadap NULL pada fungsi yang benar-benar bisa menerima NULL. - Mengira pass by reference tersedia sebagai sintaks khusus. Bahasa C hanya mengenal penerusan berupa salinan, dan pass by reference adalah pola penggunaan pointer, bukan kata kunci. Bahasa lain seperti C++ menyediakan tanda
&pada parameter, dan perbedaan itu perlu diketahui ketika membaca kode lintas bahasa.
Ringkasan#
- Pass by reference dalam C diterapkan dengan mengoper alamat variabel, sehingga dereference di dalam fungsi menjangkau variabel pemanggil.
- Fungsi
tukarbekerja karena parameternya bertipeint *dan pemanggil mengirim&xserta&y. - Parameter pointer juga menjadi saluran nilai kembali tambahan ketika satu fungsi perlu menghasilkan lebih dari satu nilai.
Referensi#
- Functions — cppreference.com — Kaidah penerusan argumen C dan kaitannya dengan penerusan alamat.
- fscanf, scanf, sscanf — cppreference.com — Contoh pustaka standar yang mewajibkan alamat variabel sebagai argumennya.