Dalam C, apakah mungkin untuk meneruskan permohonan fungsi variadic? Seperti dalam,
int my_printf(char *fmt, ...) {
fprintf(stderr, "Calling printf with fmt %s", fmt);
return SOMEHOW_INVOKE_LIBC_PRINTF;
}
Meneruskan permohonan dengan cara di atas jelas tidak sepenuhnya diperlukan dalam kasus ini (karena Anda dapat mencatat doa dengan cara lain, atau menggunakan vfprintf), tetapi basis kode yang saya kerjakan memerlukan pembungkus untuk melakukan beberapa pekerjaan aktual, dan tidak tidak memiliki (dan tidak bisa menambahkan) fungsi pembantu seperti vfprintf.
[Pembaruan: tampaknya ada beberapa kebingungan berdasarkan jawaban yang telah diberikan sejauh ini. Untuk mengungkapkan pertanyaan dengan cara lain: secara umum, dapatkah Anda membungkus beberapa fungsi variad yang sewenang-wenang tanpa mengubah definisi fungsi itu .]
Jawaban:
Jika Anda tidak memiliki fungsi analog dengan
vfprintf
yang mengambilva_list
alih - alih sejumlah variabel argumen, Anda tidak bisa melakukannya . Lihathttp://c-faq.com/varargs/handoff.html .Contoh:
sumber
Tidak secara langsung, namun itu umum (dan Anda akan menemukan hampir secara universal kasus di perpustakaan standar) untuk fungsi variadic berpasangan dengan
varargs
fungsi alternatif gaya. misalnyaprintf
/vprintf
Fungsi v ... mengambil parameter va_list, implementasi yang sering dilakukan dengan 'makro makro' kompiler khusus, tetapi Anda dijamin bahwa memanggil fungsi v ... style dari fungsi variadic seperti ini akan berfungsi:
Ini akan memberi Anda efek yang Anda cari.
Jika Anda mempertimbangkan untuk menulis fungsi pustaka variad, Anda juga harus mempertimbangkan membuat pendamping gaya va_list tersedia sebagai bagian dari pustaka. Seperti yang dapat Anda lihat dari pertanyaan Anda, itu bisa terbukti bermanfaat bagi pengguna Anda.
sumber
va_copy
sebelum meneruskanmyargs
variabel ke fungsi lain. Silakan lihat MSC39-C , di mana ia menyatakan bahwa apa yang Anda lakukan adalah perilaku yang tidak terdefinisi.va_arg()
padava_list
yang memiliki nilai tak tentu", saya tidak melakukan itu karena saya tidak pernah menggunakanva_arg
fungsi memanggil saya. The nilai darimyargs
setelah panggilan (dalam hal ini) untukvprintf
yang tak tentu (dengan asumsi bahwa hal itu kamiva_arg
). Standar mengatakan bahwamyargs
"harus diteruskan keva_end
makro sebelum referensi lebih lanjut ke [itu]"; itulah yang saya lakukan. Saya tidak perlu menyalin argumen itu karena saya tidak punya niat untuk mengulanginya melalui mereka dalam fungsi panggilan.ap
dilewatkan ke fungsi yang menggunakanva_arg(ap,type)
maka nilaiap
tidak ditentukan setelah kembalinya fungsi itu.C99 mendukung makro dengan argumen variadik ; tergantung pada kompiler Anda, Anda mungkin dapat mendeklarasikan makro yang melakukan apa yang Anda inginkan:
Namun, secara umum, solusi terbaik adalah menggunakan bentuk va_list dari fungsi yang Anda coba bungkus, jika ada.
sumber
Hampir, menggunakan fasilitas yang tersedia di
<stdarg.h>
:Perhatikan bahwa Anda perlu menggunakan
vprintf
versi daripada sekadarprintf
. Tidak ada cara untuk langsung memanggil fungsi variadic dalam situasi ini tanpa menggunakanva_list
.sumber
Karena tidak mungkin untuk meneruskan panggilan semacam itu dengan cara yang baik, kami mengatasinya dengan menyiapkan bingkai tumpukan baru dengan salinan bingkai tumpukan asli. Namun ini sangat tidak dapat diport dan membuat semua jenis asumsi , misalnya bahwa kode menggunakan frame pointer dan konvensi pemanggilan 'standar'.
File header ini memungkinkan untuk membungkus fungsi variadic untuk x86_64 dan i386 (GCC). Itu tidak bekerja untuk argumen floating-point, tetapi harus lurus ke depan untuk memperpanjang untuk mendukung mereka.
Pada akhirnya Anda dapat menjawab panggilan seperti ini:
sumber
Gunakan vfprintf:
sumber
Tidak ada cara untuk meneruskan panggilan fungsi tersebut karena satu-satunya lokasi di mana Anda dapat mengambil elemen tumpukan mentah
my_print()
. Cara biasa untuk membungkus panggilan seperti itu adalah memiliki dua fungsi, yang hanya mengubah argumen menjadi berbagaivarargs
struct, dan yang lain yang benar-benar beroperasi pada struct tersebut. Menggunakan model fungsi ganda, Anda dapat (misalnya) membungkusprintf()
dengan menginisialisasi structmy_printf()
denganva_start()
, dan kemudian meneruskannyavfprintf()
.sumber
Ya, Anda bisa melakukannya, tetapi itu agak jelek dan Anda harus tahu jumlah maksimal argumen. Lebih jauh lagi jika Anda menggunakan arsitektur di mana argumen tidak diteruskan pada stack seperti x86 (misalnya, PowerPC), Anda harus tahu apakah tipe "spesial" (double, float, altivec, dll.) Digunakan dan jika jadi, hadapi mereka sesuai. Ini bisa terasa menyakitkan dengan cepat tetapi jika Anda menggunakan x86 atau jika fungsi asli memiliki batas yang didefinisikan dengan baik dan terbatas, itu bisa bekerja. Itu masih akan menjadi hack , gunakan untuk tujuan debugging. Jangan membangun perangkat lunak Anda di sekitar itu. Bagaimanapun, berikut ini contoh yang berfungsi pada x86:
Untuk beberapa alasan, Anda tidak dapat menggunakan float dengan va_arg, gcc mengatakan mereka dikonversi menjadi dua kali lipat tetapi program macet. Itu saja menunjukkan bahwa solusi ini adalah peretasan dan tidak ada solusi umum. Dalam contoh saya, saya berasumsi bahwa jumlah maksimum argumen adalah 8, tetapi Anda dapat meningkatkan jumlah itu. Fungsi yang dibungkus juga hanya menggunakan bilangan bulat tetapi berfungsi dengan cara yang sama dengan parameter 'normal' lainnya karena selalu dilemparkan ke bilangan bulat. Fungsi target akan tahu tipenya tetapi pembungkus perantara Anda tidak perlu. Wrapper juga tidak perlu tahu jumlah argumen yang tepat karena fungsi target juga akan mengetahuinya. Untuk melakukan pekerjaan yang bermanfaat (kecuali hanya mencatat panggilan), Anda mungkin harus mengetahui keduanya.
sumber
gcc menawarkan ekstensi yang dapat melakukan ini:
__builtin_apply
dan kerabat. Lihat Membuat Panggilan Fungsi di manual gcc.Sebuah contoh:
Cobalah di godbolt
Ada beberapa peringatan dalam dokumentasi yang mungkin tidak berfungsi dalam situasi yang lebih rumit. Dan Anda harus menuliskan kode ukuran maksimum untuk argumen (di sini saya menggunakan 1000). Tapi itu bisa menjadi alternatif yang masuk akal untuk pendekatan lain yang melibatkan membedah tumpukan dalam bahasa C atau bahasa assembly.
sumber
Pada dasarnya ada tiga opsi.
Pertama adalah untuk tidak meneruskannya tetapi untuk menggunakan implementasi variadik dari fungsi target Anda dan tidak meneruskan elips. Yang lain adalah menggunakan makro variadic. Opsi ketiga adalah semua hal yang saya lewatkan.
Saya biasanya memilih opsi satu karena saya merasa ini sangat mudah ditangani. Opsi dua memiliki kelemahan karena ada beberapa batasan untuk memanggil macro variadic.
Berikut ini beberapa contoh kode:
sumber
Cara terbaik untuk melakukan ini adalah
sumber
Tidak yakin apakah ini membantu menjawab pertanyaan OP karena saya tidak tahu mengapa pembatasan untuk menggunakan fungsi pembantu seperti vfprintf dalam fungsi wrapper berlaku. Saya pikir masalah utama di sini adalah meneruskan daftar argumen variadik tanpa menafsirkannya sulit. Apa yang mungkin, adalah untuk melakukan pemformatan (menggunakan fungsi pembantu seperti vfprintf: vsnprintf) dan meneruskan output yang diformat ke fungsi yang dibungkus dengan argumen variadic (yaitu tidak mengubah definisi fungsi yang dibungkus). Jadi, ini dia:
Saya menemukan solusi ini di sini .
sumber