Meneruskan argumen variabel ke fungsi lain yang menerima daftar argumen variabel

114

Jadi saya punya 2 fungsi yang keduanya memiliki argumen yang mirip

void example(int a, int b, ...);
void exampleB(int b, ...);

Sekarang examplepanggilan exampleB, tapi bagaimana saya bisa meneruskan variabel dalam daftar argumen variabel tanpa memodifikasi exampleB(karena ini sudah digunakan di tempat lain juga).

Tidak tersedia
sumber
6
kemungkinan duplikat Teruskan pemanggilan fungsi variadic di C
Greg Hewgill
2
Solusi yang satu itu menggunakan vprintf, dan bukan itu masalahnya di sini.
Tidak Tersedia
Ini terkait dengan, tetapi jelas tidak sama dengan, duplikat yang diusulkan: Meneruskan pemanggilan fungsi variadic di C?
Jonathan Leffler

Jawaban:

127

Anda tidak dapat melakukannya secara langsung; Anda harus membuat fungsi yang membutuhkan va_list:

#include <stdarg.h>

static void exampleV(int b, va_list args);

void exampleA(int a, int b, ...)    // Renamed for consistency
{
    va_list args;
    do_something(a);                // Use argument a somehow
    va_start(args, b);
    exampleV(b, args);
    va_end(args);
}

void exampleB(int b, ...)
{
    va_list args;
    va_start(args, b);
    exampleV(b, args);
    va_end(args);
}

static void exampleV(int b, va_list args)
{
    ...whatever you planned to have exampleB do...
    ...except it calls neither va_start nor va_end...
}
Jonathan Leffler
sumber
2
Dugaan saya harus melakukan sesuatu seperti ini, masalahnya adalah fungsi contoh pada dasarnya adalah pembungkus untuk vsprintf dan tidak banyak lagi: /
Tidak Tersedia
@Xeross: Perhatikan bahwa ini tidak mengubah spesifikasi eksternal dari apa yang dilakukan exampleB - ini hanya mengubah implementasi internal. Saya tidak yakin apa masalahnya.
Jonathan Leffler
Apakah parameter pertama diperlukan atau dapatkah semua parameter menjadi variadic?
Qwerty
1
@ Qwerty: Sintaks ini memerlukan argumen pertama bernama ke fungsi yang mengambil daftar argumen variabel dengan , ...tanda tangan. Jika Anda telah mengonversi argumen variabel menjadi a va_list, Anda bisa meneruskan va_listke fungsi lain yang hanya membutuhkan a va_list, tetapi fungsi itu (atau yang dipanggilnya) harus memiliki cara untuk mengetahui apa yang ada di va_list.
Jonathan Leffler
46

Mungkin melempar batu ke kolam di sini, tetapi tampaknya bekerja cukup baik dengan template variadic C ++ 11:

#include <stdio.h>

template<typename... Args> void test(const char * f, Args... args) {
  printf(f, args...);
}

int main()
{
  int a = 2;
  test("%s\n", "test");
  test("%s %d %d %p\n", "second test", 2, a, &a);
}

Setidaknya, ini berfungsi dengan g++.

Vincent Fourmond
sumber
Saya bingung - apakah ini pendekatan yang sah menggunakan C ++> = 11?
Duncan Jones
@DuncanJones Ya, paket akan diperluas olehargs...
Swordfish
15

Anda harus membuat versi fungsi ini yang mengambil va_list, dan meneruskannya. Lihat vprintfsebagai contoh:

int vprintf ( const char * format, va_list arg );
sepuluh empat
sumber
5

Saya juga ingin membungkus printf dan menemukan jawaban yang membantu di sini:

Cara melewatkan sejumlah variabel argumen ke printf / sprintf

Saya sama sekali tidak tertarik dengan kinerja (saya yakin potongan kode ini dapat ditingkatkan dalam beberapa cara, silakan melakukannya :)), ini hanya untuk pencetakan debug umum jadi saya melakukan ini:

//Helper function
std::string osprintf(const char *fmt, ...)
{
    va_list args;
    char buf[1000];
    va_start(args, fmt);
    vsnprintf(buf, sizeof(buf), fmt, args );
    va_end(args);
    return buf;
}

yang kemudian bisa saya gunakan seperti ini

Point2d p;

cout << osprintf("Point2d: (%3i, %3i)", p.x, p.y);
instead of for example:
cout << "Point2d: ( " << setw(3) << p.x << ", " << p.y << " )";

C ++ ostreams indah dalam beberapa aspek, tetapi praktis menjadi mengerikan jika Anda ingin mencetak sesuatu seperti ini dengan beberapa string kecil seperti tanda kurung, titik dua, dan koma yang disisipkan di antara angka-angka tersebut.

Axel
sumber
2

Kebetulan, banyak implementasi C memiliki variasi v? Printf internal yang IMHO seharusnya menjadi bagian dari standar C. Detail pastinya bervariasi, tetapi implementasi tipikal akan menerima struct yang berisi penunjuk fungsi keluaran karakter dan informasi yang mengatakan apa yang seharusnya terjadi. Ini memungkinkan printf, sprintf, dan fprintf untuk semua menggunakan mekanisme 'inti' yang sama. Misalnya, vsprintf mungkin seperti ini:

batal s_out (PRINTF_INFO * p_inf, char ch)
{
  (* (p_inf-> destptr) ++) = ch;
  p_inf-> hasil ++;
}

int vsprintf (char * dest, const char * fmt, va_list args)
{
  PRINTF_INFO p_inf;
  p_inf.destptr = dest;
  p_inf.result = 0;
  p_inf.func = s_out;
  core_printf (& p_inf, fmt, args);
}

Fungsi core_printf kemudian memanggil p_inf-> func untuk setiap karakter yang akan menjadi keluaran; fungsi keluaran kemudian dapat mengirim karakter ke konsol, file, string, atau yang lainnya. Jika implementasi seseorang mengekspos fungsi core_printf (dan mekanisme pengaturan apa pun yang digunakannya), seseorang dapat memperluasnya dengan segala macam variasi.

supercat
sumber
1

Cara yang mungkin adalah menggunakan #define:

#define exampleB(int b, ...)  example(0, b, __VA_ARGS__)
Shai
sumber
0

Berdasarkan komentar yang Anda bungkus vsprintf, dan ini ditandai sebagai C ++ Saya sarankan untuk tidak mencoba melakukan ini, tetapi ubah antarmuka Anda untuk menggunakan C ++ iostreams sebagai gantinya. Mereka memiliki keunggulan dibandingkan fungsi printgaris, seperti keamanan tipe dan mampu mencetak item yang printftidak dapat ditangani. Beberapa pengerjaan ulang sekarang dapat menghemat banyak rasa sakit di masa depan.

Mark B
sumber
Keuntungan apa yang Anda maksud?
cjcurrie
@cjcurrie: keuntungannya adalah keamanan tipe, bahkan dengan tipe yang ditentukan pengguna. Fungsi C tidak bisa menangani tipe yang ditentukan pengguna sama sekali, tentu saja.
Jonathan Leffler
-1

Dengan menggunakan standar C ++ 0x baru, Anda mungkin dapat menyelesaikan ini menggunakan template variadic atau bahkan mengonversi kode lama itu ke sintaks template baru tanpa merusak apa pun.

David
sumber
sayangnya ini tidak mungkin dalam semua kasus - coba gunakan lambda
serup
-1

Ini adalah satu-satunya cara untuk melakukannya .. dan cara terbaik untuk melakukannya juga ..

static BOOL(__cdecl *OriginalVarArgsFunction)(BYTE variable1, char* format, ...)(0x12345678); //TODO: change address lolz

BOOL __cdecl HookedVarArgsFunction(BYTE variable1, char* format, ...)
{
    BOOL res;

    va_list vl;
    va_start(vl, format);

    // Get variable arguments count from disasm. -2 because of existing 'format', 'variable1'
    uint32_t argCount = *((uint8_t*)_ReturnAddress() + 2) / sizeof(void*) - 2;
    printf("arg count = %d\n", argCount);

    // ((int( __cdecl* )(const char*, ...))&oldCode)(fmt, ...);
    __asm
    {
        mov eax, argCount
        test eax, eax
        je noLoop
        mov edx, vl
        loop1 :
        push dword ptr[edx + eax * 4 - 4]
        sub eax, 1
        jnz loop1
        noLoop :
        push format
        push variable1
        //lea eax, [oldCode] // oldCode - original function pointer
        mov eax, OriginalVarArgsFunction
        call eax
        mov res, eax
        mov eax, argCount
        lea eax, [eax * 4 + 8] //+8 because 2 parameters (format and variable1)
        add esp, eax
    }
    return res;
}
SSpoke
sumber