Variadic function adalah fungsi yang menerima jumlah argumen yang tidak tetap—bisa nol, satu, atau sepuluh. Contoh paling terkenal adalah printf dan keluarganya: kita bisa menulis printf("halo") atau printf("%d + %d = %d", a, b, a+b) dengan signature yang sama. C menyediakan mekanisme variadic lewat header <stdarg.h> dan tipe va_list. Pada chapter ini kita akan menulis sum, rata_rata, dan fungsi log_line yang meniru pola printf. Pemahaman mendalam tentang <stdarg.h> akan dibahas di chapter berikutnya.

Mengapa Variadic Function Diperlukan #

Bayangkan kita ingin menulis fungsi sum yang bisa menerima berapapun angka. Tanpa variadic, kita harus menulis overload (yang tidak ada di C) atau memakai array: sum(int *arr, int n). Pendekatan array memaksa pemanggil mengemas argumen ke array, tidak nyaman untuk panggilan cepat dengan beberapa nilai. Variadic memberi solusi: pemanggil menulis argumen langsung, dan fungsi membaca satu per satu lewat va_list.

c
#include <stdio.h>
#include <stdarg.h>

/* Fungsi variadic: jumlahkan count angka */
int sum(int count, ...) {
    va_list ap;
    va_start(ap, count);
    int total = 0;
    for (int i = 0; i < count; i++) {
        total += va_arg(ap, int);
    }
    va_end(ap);
    return total;
}

/* Fungsi variadic: rata-rata double */
double rata_rata(int count, ...) {
    va_list ap;
    va_start(ap, count);
    double total = 0.0;
    for (int i = 0; i < count; i++) {
        total += va_arg(ap, double);
    }
    va_end(ap);
    return count > 0 ? total / count : 0.0;
}

/* Cetak pasangan key-value string */
void log_pairs(int count, ...) {
    va_list ap;
    va_start(ap, count);
    for (int i = 0; i < count; i++) {
        const char *key = va_arg(ap, const char *);
        const char *val = va_arg(ap, const char *);
        printf("  %s = %s\n", key, val);
    }
    va_end(ap);
}

int main(void) {
    printf("sum(3, 10, 20, 30) = %d\n", sum(3, 10, 20, 30));
    printf("sum(5, 1, 2, 3, 4, 5) = %d\n", sum(5, 1, 2, 3, 4, 5));
    printf("sum(0) = %d\n", sum(0));

    printf("rata_rata(4, 1.0, 2.0, 3.0, 4.0) = %.2f\n",
           rata_rata(4, 1.0, 2.0, 3.0, 4.0));

    printf("pasangan:\n");
    log_pairs(2, "user", "budi", "halaman", "/home");

    return 0;
}

Output yang Dihasilkan:

text
sum(3, 10, 20, 30) = 60
sum(5, 1, 2, 3, 4, 5) = 15
sum(0) = 0
rata_rata(4, 1.0, 2.0, 3.0, 4.0) = 2.50
pasangan:
  user = budi
  halaman = /home

Mari kita telaah baris-baris kode di atas:

  1. Baris 6 (int sum(int count, ...)): tanda ... (ellipsis) menandakan fungsi menerima jumlah argumen variabel setelah count. Standar C mensyaratkan parameter bernama terakhir sebelum ...—count menjadi anchor yang dipakai va_start.
  2. Baris 8 (va_start(ap, count);): makro va_start menginisialisasi va_list ap agar menunjuk ke argumen pertama setelah count. Wajib dipanggil sebelum va_arg.
  3. Baris 11 (total += va_arg(ap, int);): makro va_arg mengembalikan argumen berikutnya dan secara otomatis memajukan ap. Tipe int harus persis cocok dengan tipe argumen yang dikirim pemanggil—compiler tidak melakukan konversi otomatis di sini.
  4. Baris 13 (va_end(ap);): makro va_end membersihkan va_list. Wajib dipanggil sebelum fungsi kembali—pada beberapa platform va_list menyimpan resource yang harus dilepas.
  5. Baris 18-27 (rata_rata): sama dengan sum tetapi tipe double. Perhatikan bahwa va_arg(ap, double) akan salah bila pemanggil mengirim int—karena default argument promotion, float otomatis menjadi double, tetapi int tetap int. Pemanggil harus konsisten mengirim double.
  6. Baris 30-39 (log_pairs): membaca dua argumen string per iterasi. Pola key-value umum di API logging variadic.

Pola printf-Like dengan vprintf #

Sering kita ingin menulis fungsi yang menerima format string dan argumen, lalu meneruskan ke printf dengan prefix tambahan. C menyediakan vprintf, vsprintf, dan vsnprintf yang menerima va_list bukan ... langsung.

c
#include <stdio.h>
#include <stdarg.h>
#include <string.h>

/* Wrapper printf-like yang menambah prefix ke setiap baris */
void log_line(const char *prefix, const char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    printf("[%s] ", prefix);
    vprintf(fmt, ap);
    printf("\n");
    va_end(ap);
}

/* Hitung maksimum dari sekian int (akhir dengan 0 sentinel) */
int maxn(int first, ...) {
    va_list ap;
    va_start(ap, first);
    int best = first;
    int v;
    while ((v = va_arg(ap, int)) != 0) {
        if (v > best) best = v;
    }
    va_end(ap);
    return best;
}

int main(void) {
    log_line("INFO",  "memulai aplikasi versi %d.%d", 1, 7);
    log_line("ERROR", "gagal membuka %s (kode %d)", "data.txt", 13);
    log_line("WARN",  "penggunaan memori %d%%", 87);

    printf("max(3, 9, 5, 12, 7, 0) = %d\n", maxn(3, 9, 5, 12, 7, 0));
    printf("max(42, 0) = %d\n", maxn(42, 0));

    return 0;
}

Output yang Dihasilkan:

text
[INFO] memulai aplikasi versi 1.7
[ERROR] gagal membuka data.txt (kode 13)
[WARN] penggunaan memori 87%
max(3, 9, 5, 12, 7, 0) = 12
max(42, 0) = 42

Fungsi log_line menerima ..., lalu meneruskan va_list ke vprintf. Pola ini menghindari kita menulis parser format sendiri—pustaka standar sudah melakukannya dengan aman (memeriksa %d, %s, dll). Fungsi maxn memakai pola berbeda: argumen pertama eksplisit (first), diikuti ... dengan sentinel 0 sebagai penanda akhir.

Default Argument Promotion #

Pada fungsi variadic, argumen menjalani default argument promotion:

  • char, short, signed char, unsigned char, unsigned short dipromosikan ke int (atau unsigned int bila int tidak muat).
  • float dipromosikan ke double.

Karena itu, va_arg(ap, char) adalah salah—harus va_arg(ap, int). Demikian pula va_arg(ap, float) harus va_arg(ap, double).