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_listadalah 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): menginisialisasiapagar menunjuk ke argumen pertama setelah parameter bernamalast. Wajib dipanggil sebelumva_arg.va_arg(ap, T): mengembalikan argumen berikutnya dengan tipeT, lalu memajukanap. TipeTharus 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 untukva_listnon-register).
#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:
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:
- Baris 8 (
va_list ap;): deklarasi variabelapbertipeva_list. Lokasi deklarasi bebas, tetapi harus dilakukan sebelumva_start. - Baris 9 (
va_start(ap, count);): parameter keduacountadalah parameter bernama terakhir sebelum.... Makro memakainya sebagai anchor untuk menemukan awal daftar argumen variadic pada stack atau register. - Baris 12 (
va_arg(ap, int)): setiap pemanggilan mengembalikan satu argumen dan memajukanap. Tipe harus persisint—karena default argument promotion,char/shortsudah dipromosikan menjadiintsebelum mencapai fungsi. - Baris 14 (
va_end(ap);): wajib dipanggil sebelumreturnagar resource internalapdilepas. - Baris 19-26 (
total_karakter): contohva_arg(ap, const char *). Tipe pointer tidak mengalami promosi—harus persis cocok dengan apa yang dikirim pemanggil. - Baris 30-35 (
log_msg): pola klasik meneruskanva_listkevprintf.vprintfmengonsumsiapdengan aman karena pustaka standar tahu cara memajukanva_listsesuai 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.
#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:
== 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_argmemajukan, tidak adava_prev. Solusinya hanyava_copysebelum iterasi dimulai. - Tidak aman terhadap tipe:
va_arg(ap, int)ketika argumen sebenarnyadoubleadalah undefined behavior yang sering diam-diam merusak data.