Pada chapter sebelumnya kita memakai variadic function tanpa menelaah detail mekanismenya. Sekarang kita akan masuk lebih dalam ke empat makro inti dari <stdarg.h>: va_list, va_start, va_arg, dan va_end. Kita juga akan mempelajari va_copy (C99) yang memungkinkan kita melakukan iterasi dua kali pada daftar argumen yang sama. Memahami makro ini penting karena banyak API internal C (terutama wrapper logging, format, dan serialisasi) memerlukan manipulasi langsung va_list alih-alih sekadar memakai ....

Empat Makro Inti <stdarg.h> #

Berikut adalah peran masing-masing makro:

  • va_list adalah tipe opaque yang menyimpan state iterasi atas argumen variadic. Implementasi internalnya bergantung pada platform—bisa berupa struct, array, atau register list.
  • va_start(ap, last): menginisialisasi ap agar menunjuk ke argumen pertama setelah parameter bernama last. Wajib dipanggil sebelum va_arg.
  • va_arg(ap, T): mengembalikan argumen berikutnya dengan tipe T, lalu memajukan ap. Tipe T harus persis cocok dengan tipe argumen yang dikirim pemanggil (setelah default argument promotion).
  • va_end(ap): menyelesaikan iterasi. Wajib dipanggil sebelum fungsi kembali. Pada beberapa platform, makro ini membebaskan resource internal (misalnya alokasi memori untuk va_list non-register).
c
#include <stdio.h>
#include <stdarg.h>
#include <string.h>

/* Hitung total count angka */
long sum_long(int count, ...) {
    va_list ap;
    va_start(ap, count);
    long total = 0;
    for (int i = 0; i < count; i++) {
        total += va_arg(ap, int);
    }
    va_end(ap);
    return total;
}

/* Hitung jumlah karakter semua string variadic */
size_t total_karakter(int count, ...) {
    va_list ap;
    va_start(ap, count);
    size_t total = 0;
    for (int i = 0; i < count; i++) {
        const char *s = va_arg(ap, const char *);
        if (s) total += strlen(s);
    }
    va_end(ap);
    return total;
}

/* Fungsi yang meniru printf dan mengembalikan jumlah byte */
int log_msg(const char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    int n = vprintf(fmt, ap);
    va_end(ap);
    return n;
}

int main(void) {
    /* va_arg dengan int */
    printf("sum_long(3, 10, 20, 30) = %ld\n", sum_long(3, 10, 20, 30));

    /* va_arg dengan pointer */
    printf("total_karakter(3, \"Halo\", \"Dunia\", \"!\") = %zu\n",
           total_karakter(3, "Halo", "Dunia", "!"));

    /* vprintf via va_list */
    int n = log_msg("log: %d + %d = %d\n", 2, 3, 5);
    printf("(panjang pesan: %d karakter)\n", n);

    return 0;
}

Output yang Dihasilkan:

text
sum_long(3, 10, 20, 30) = 60
total_karakter(3, "Halo", "Dunia", "!") = 10
log: 2 + 3 = 5
(panjang pesan: 15 karakter)

Mari kita telaah baris-baris kode di atas:

  1. Baris 8 (va_list ap;): deklarasi variabel ap bertipe va_list. Lokasi deklarasi bebas, tetapi harus dilakukan sebelum va_start.
  2. Baris 9 (va_start(ap, count);): parameter kedua count adalah parameter bernama terakhir sebelum .... Makro memakainya sebagai anchor untuk menemukan awal daftar argumen variadic pada stack atau register.
  3. Baris 12 (va_arg(ap, int)): setiap pemanggilan mengembalikan satu argumen dan memajukan ap. Tipe harus persis int—karena default argument promotion, char/short sudah dipromosikan menjadi int sebelum mencapai fungsi.
  4. Baris 14 (va_end(ap);): wajib dipanggil sebelum return agar resource internal ap dilepas.
  5. Baris 19-26 (total_karakter): contoh va_arg(ap, const char *). Tipe pointer tidak mengalami promosi—harus persis cocok dengan apa yang dikirim pemanggil.
  6. Baris 30-35 (log_msg): pola klasik meneruskan va_list ke vprintf. vprintf mengonsumsi ap dengan aman karena pustaka standar tahu cara memajukan va_list sesuai format specifier.

va_copy untuk Iterasi Dua Pass #

va_list bersifat single-use dalam arti setelah va_arg mencapai akhir, kita tidak bisa memajukan mundur. Bila fungsi perlu melakukan dua kali iterasi (misalnya pertama untuk menghitung total, kedua untuk mencetak elemen), kita memerlukan va_copy (C99) untuk membuat duplikat va_list.

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

/* Meneruskan va_list ke fungsi lain */
void inner_print(va_list ap, int count) {
    for (int i = 0; i < count; i++) {
        int v = va_arg(ap, int);
        printf("  arg[%d] = %d\n", i, v);
    }
}

void forward_args(int count, ...) {
    va_list ap;
    va_start(ap, count);
    inner_print(ap, count);
    va_end(ap);
}

/* Copy va_list untuk dua pass */
void two_pass(int count, ...) {
    va_list ap, ap2;
    va_start(ap, count);
    va_copy(ap2, ap);  /* salin state va_list */

    /* pass 1: hitung total */
    int total = 0;
    for (int i = 0; i < count; i++) total += va_arg(ap, int);
    va_end(ap);
    printf("total = %d\n", total);

    /* pass 2: cetak tiap elemen */
    for (int i = 0; i < count; i++) {
        int v = va_arg(ap2, int);
        printf("  elemen[%d] = %d (avg=%.2f)\n", i, v, (double)v / total);
    }
    va_end(ap2);
}

int main(void) {
    printf("== forward_args ==\n");
    forward_args(3, 10, 20, 30);

    printf("== two_pass ==\n");
    two_pass(4, 8, 4, 6, 2);

    return 0;
}

Output yang Dihasilkan:

text
== forward_args ==
  arg[0] = 10
  arg[1] = 20
  arg[2] = 30
== two_pass ==
total = 20
  elemen[0] = 8 (avg=0.40)
  elemen[1] = 4 (avg=0.20)
  elemen[2] = 6 (avg=0.30)
  elemen[3] = 2 (avg=0.10)

Fungsi forward_args menunjukkan bahwa va_list dapat diteruskan ke fungsi lain. Penting untuk diingat bahwa fungsi penerima harus memanggil va_end sendiri bila memakai va_copy secara lokal, tetapi bila hanya mengonsumsi va_list tanpa menyalin, pemanggil yang bertanggung jawab atas va_end.

Fungsi two_pass memakai va_copy(ap2, ap) untuk membuat duplikat. Setelah pass pertama mengonsumsi ap sampai akhir, pass kedua memakai ap2 yang masih menunjuk ke awal. Setiap salinan va_list membutuhkan va_end terpisah—tidak boleh dibagikan.

Keterbatasan va_list #

  • Tidak ada cara introspektif mengetahui tipe atau jumlah argumen tanpa informasi eksternal (format string, count, atau sentinel).
  • Tidak bisa mundur: setelah va_arg memajukan, tidak ada va_prev. Solusinya hanya va_copy sebelum iterasi dimulai.
  • Tidak aman terhadap tipe: va_arg(ap, int) ketika argumen sebenarnya double adalah undefined behavior yang sering diam-diam merusak data.