Saat program kita tumbuh menjadi ratusan atau ribuan baris, menumpuk seluruh logika di dalam main akan membuat kode sulit dibaca, diuji, dan dirawat. Fungsi hadir sebagai unit organisasi terkecil yang memungkinkan kita memecah program besar menjadi bagian-bagian kecil dengan tanggung jawab tunggal. Setiap fungsi menerima input melalui parameter, melakukan pekerjaan tertentu, lalu mengembalikan output melalui return value. Pada chapter ini kita akan menelusuri apa itu fungsi, mengapa fungsi penting, bagaimana compiler memperlakukannya, serta pola dasar pemanggilan fungsi di C.

Apa Itu Fungsi #

Fungsi adalah blok kode bernama yang dapat dieksekusi berkali-kali dari berbagai titik di program. Saat kita memanggil sebuah fungsi, compiler menyisipkan kode call yang menyimpan alamat pemanggil (return address) ke call stack, melompat ke alamat fungsi, mengeksekusi body-nya, lalu melompat kembali ke pemanggil dan mengembalikan hasil lewat register (biasanya RAX pada x86-64). Mekanisme ini membuat fungsi menjadi unit reusable: kita menulis satu kali, memanggil berkali-kali.

Sebuah fungsi di C secara umum memiliki empat komponen:

  1. Return type — tipe data nilai yang dikembalikan (misal int, double, atau void bila tidak ada nilai).
  2. Nama fungsi — identifier yang dipakai untuk memanggilnya.
  3. Daftar parameter (boleh kosong) — variabel lokal yang menerima input dari pemanggil.
  4. Body — blok kode di dalam { } yang menjalankan pekerjaan fungsi.

Visualisasi Konsep #

Diagram berikut memperlihatkan alur pemanggilan fungsi: caller menyimpan return address, melompat ke callee, callee mengeksekusi body, lalu mengembalikan kontrol dan return value ke caller:

Pada diagram di atas, alur pemanggilan fungsi melibatkan empat langkah berurutan. Pertama, pemanggil menyimpan return address dan argumen ke call stack untuk bisa melanjutkan eksekusi setelah fungsi kembali. Kedua, kontrol melompat ke alamat fungsi yang dipanggil (call instruction). Ketiga, fungsi mengeksekusi body-nya dan menyimpan return value (biasanya lewat register RAX pada x86-64). Keempat, instruksi ret mengembalikan kontrol ke alamat pemanggil dan stack frame fungsi dipop. Inilah mengapa fungsi menjadi unit reusable: pemanggil cukup mengirim argumen dan menerima hasil, tanpa perlu tahu detail implementasi body.

Fungsi Sederhana Tanpa dan Dengan Parameter #

Kita mulai dengan tiga variasi fungsi sekaligus: fungsi tanpa parameter, fungsi berparameter, dan fungsi yang mengembalikan nilai. Ketiganya dijaga tetap kecil agar perbedaan strukturnya mudah dilihat.

c
#include <stdio.h>

/* Fungsi sederhana tanpa parameter, tanpa return */
void sapa(void) {
    printf("Halo, selamat datang di belajar C!\n");
}

/* Fungsi dengan satu parameter */
void sapa_user(const char *nama) {
    printf("Halo %s, semoga harimu menyenangkan.\n", nama);
}

/* Fungsi dengan parameter dan return value */
int tambah(int a, int b) {
    return a + b;
}

int main(void) {
    sapa();
    sapa_user("Budi");
    int hasil = tambah(7, 5);
    printf("7 + 5 = %d\n", hasil);
    return 0;
}

Output yang Dihasilkan:

text
Halo, selamat datang di belajar C!
Halo Budi, semoga harimu menyenangkan.
7 + 5 = 12

Mari kita telaah baris-baris kode di atas:

  1. Baris 4 (void sapa(void)): deklarasi fungsi bernama sapa. Kata kunci void di posisi parameter berarti fungsi ini tidak menerima argument sama sekali; void di posisi return type berarti fungsi tidak mengembalikan nilai.
  2. Baris 5 (printf(...)): body fungsi sapa hanya mencetak sapaan. Setelah body selesai, kontrol kembali ke pemanggil.
  3. Baris 9 (void sapa_user(const char *nama)): fungsi ini menerima satu parameter bertipe const char * (pointer ke karakter konstan). Kwalifier const memberi sinyal ke compiler bahwa string nama tidak akan dimodifikasi di dalam fungsi.
  4. Baris 13 (int tambah(int a, int b)): fungsi ini menerima dua int dan mengembalikan int.
  5. Baris 14 (return a + b;): ekspresi a + b dievaluasi, hasilnya dikirim balik ke pemanggil sebagai return value.
  6. Baris 19 (sapa();): pemanggilan tanpa argument. Tanda kurung tetap wajib meskipun daftar parameter kosong.
  7. Baris 20 (sapa_user("Budi");): string literal "Budi" menjadi argument pertama (dan satu-satunya). Compiler mengonversi literal ini menjadi pointer ke array karakter konstan.
  8. Baris 21 (int hasil = tambah(7, 5);): nilai 7 dipetakan ke parameter a, 5 ke parameter b. Setelah fungsi tambah selesai, hasil 12 ditugaskan ke variabel hasil.

Mengapa Fungsi Penting #

Fungsi memberi tiga manfaat utama: reusability (kita menulis logika sekali dan memanggilnya berkali-kali), abstraction (pemanggil cukup tahu apa yang dilakukan fungsi tanpa perlu tahu bagaimana), serta testability (kita dapat menguji fungsi secara terpisah dari main). Mari kita lihat contoh penggunaan ulang sebuah fungsi dengan argumen berbeda.

c
#include <stdio.h>

/* Fungsi yang menghitung luas persegi panjang */
int hitung_luas(int panjang, int lebar) {
    return panjang * lebar;
}

/* Fungsi yang mencetak garis pemisah */
void cetak_garis(void) {
    printf("--------------------------\n");
}

int main(void) {
    int panjang = 8;
    int lebar = 4;

    cetak_garis();
    int luas = hitung_luas(panjang, lebar);
    printf("Luas persegi panjang: %d\n", luas);
    cetak_garis();

    /* Memanggil fungsi berulang dengan argumen berbeda */
    int luas2 = hitung_luas(10, 3);
    printf("Luas lain: %d\n", luas2);
    cetak_garis();
    return 0;
}

Output yang Dihasilkan:

text
--------------------------
Luas persegi panjang: 32
--------------------------
Luas lain: 30
--------------------------

Pada contoh di atas, fungsi hitung_luas dipanggil dua kali dengan argumen yang berbeda—(8, 4) menghasilkan 32, sedangkan (10, 3) menghasilkan 30. Satu definisi fungsi melayani dua kasus berbeda. Fungsi cetak_garis dipanggil tiga kali, menunjukkan bahwa fungsi void (tanpa return value) juga tetap reusable untuk pekerjaan yang berulang seperti mencetak pemisah visual.

Ringkasan Konsep #

Fungsi adalah unit reusable yang menggabungkan input, proses, dan output ke dalam satu nama. Compiler menerjemahkan pemanggilan fungsi menjadi instruksi call yang melestarikan return address di call stack, sehingga eksekusi dapat kembali ke pemanggil dengan rapi. Dengan memecah program menjadi fungsi-fungsi kecil bertanggung jawab tunggal, kita memperoleh kode yang lebih mudah dibaca, diuji, dan dirawat. Pada chapter-chapter berikutnya kita akan menelusuri detail deklarasi, definisi, parameter, return value, dan scope yang menyertai penggunaan fungsi di C.