Melewati jumlah variabel argumen di sekitar

333

Katakanlah saya memiliki fungsi C yang mengambil sejumlah variabel argumen: Bagaimana saya bisa memanggil fungsi lain yang mengharapkan sejumlah variabel argumen dari dalamnya, melewati semua argumen yang masuk ke fungsi pertama?

Contoh:

void format_string(char *fmt, ...);

void debug_print(int dbg_lvl, char *fmt, ...) {
    format_string(fmt, /* how do I pass all the arguments from '...'? */);
    fprintf(stdout, fmt);
 }
Vicent Marti
sumber
4
Contoh Anda terlihat agak aneh bagi saya, karena Anda meneruskan fmt ke format_string () dan fprintf (). Haruskah format_string () mengembalikan string baru?
Kristopher Johnson
2
Contoh tidak masuk akal. Itu hanya untuk menunjukkan garis besar kode.
Vicent Marti
162
"harus googled": Saya tidak setuju. Google memiliki banyak suara (informasi tidak jelas, seringkali membingungkan). Memiliki jawaban yang baik (dipilih, diterima) di stackoverflow sangat membantu!
Ansgar
71
Hanya untuk menimbang: Saya datang ke pertanyaan ini dari google, dan karena itu stack overflow sangat yakin bahwa jawabannya akan bermanfaat. Jadi tanyakan saja!
Tenpn
32
@Ilya: jika tidak ada yang pernah menuliskan hal-hal di luar Google, tidak akan ada informasi untuk dicari di Google.
Erik Kaplun

Jawaban:

211

Untuk meneruskan elips, Anda harus mengubahnya menjadi va_list dan menggunakannya va_list di fungsi kedua Anda. Secara khusus;

void format_string(char *fmt,va_list argptr, char *formatted_string);


void debug_print(int dbg_lvl, char *fmt, ...) 
{    
 char formatted_string[MAX_FMT_SIZE];

 va_list argptr;
 va_start(argptr,fmt);
 format_string(fmt, argptr, formatted_string);
 va_end(argptr);
 fprintf(stdout, "%s",formatted_string);
}
SmacL
sumber
3
Kode diambil dari pertanyaan, dan benar-benar hanya sebuah ilustrasi tentang cara mengubah elips daripada apa pun yang fungsional. Jika Anda melihatnya format_stringtidak akan berguna juga, karena harus melakukan modifikasi in-situ ke fmt, yang tentunya juga tidak boleh dilakukan. Opsi akan termasuk menyingkirkan format_string sama sekali dan menggunakan vfprintf, tetapi itu membuat asumsi tentang apa sebenarnya format_string, atau format_string mengembalikan string yang berbeda. Saya akan mengedit jawaban untuk menunjukkan yang terakhir.
SmacL
1
Jika string format Anda kebetulan menggunakan perintah format string yang sama dengan printf, Anda juga bisa mendapatkan beberapa kompiler seperti gcc dan clang untuk memberi Anda peringatan jika string format Anda tidak kompatibel dengan argumen aktual yang dilewatkan. Lihat atribut fungsi GCC ' format 'untuk detail lebih lanjut: gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html .
Doug Richardson
1
Ini sepertinya tidak berfungsi jika Anda melewatkan args dua kali berturut-turut.
fotanus
2
@ botani: jika Anda memanggil fungsi dengan argptrdan fungsi yang dipanggil menggunakan argptrsama sekali, satu-satunya hal yang aman untuk dilakukan adalah memanggil va_end()dan kemudian restart va_start(argptr, fmt);untuk menginisialisasi ulang. Atau Anda dapat menggunakan va_copy()jika sistem Anda mendukungnya (C99 dan C11 memerlukannya; C89 / 90 tidak).
Jonathan Leffler
1
Harap dicatat bahwa komentar @ ThomasPadron-McCarthy sekarang kedaluwarsa dan fprintf terakhir tidak masalah.
Frederick
59

Tidak ada cara untuk memanggil (misalnya) printf tanpa mengetahui berapa banyak argumen yang Anda sampaikan, kecuali Anda ingin masuk ke trik nakal dan non-portabel.

Solusi yang umum digunakan adalah untuk selalu memberikan bentuk alternatif dari fungsi vararg, sehingga printfmemiliki vprintfyang va_listmenggantikan .... The ...versi hanya pembungkus sekitar va_listversi.


sumber
Perhatikan bahwa vsyslogini tidak sesuai dengan POSIX .
patryk.beza
53

Fungsi Variadik bisa berbahaya . Ini trik yang lebih aman:

   void func(type* values) {
        while(*values) {
            x = *values++;
            /* do whatever with x */
        }
    }

func((type[]){val1,val2,val3,val4,0});
Rose Perrone
sumber
11
Yang lebih baik adalah trik ini: #define callVardicMethodSafely(values...) ({ values *v = { values }; _actualFunction(values, sizeof(v) / sizeof(*v)); })
Richard J. Ross III
5
@ RichardJ.RossIII Saya berharap Anda memperluas komentar Anda, sulit dibaca seperti ini, saya tidak bisa melihat ide di balik kode dan itu benar-benar terlihat sangat menarik dan berguna.
penelope
5
@ArtOfWarfare saya tidak yakin saya setuju bahwa ini merupakan hack yang buruk, Rose memiliki solusi yang bagus tetapi melibatkan pengetikan func ((tipe []) {val1, val2, 0}); yang terasa kikuk, jika Anda memiliki #define func_short_cut (...) func ((ketik []) { VA_ARGS }); maka Anda cukup memanggil func_short_cut (1, 2, 3, 4, 0); yang memberi Anda sintaks yang sama dengan fungsi variadic normal dengan manfaat tambahan dari trik rapi Rose ... apa masalahnya di sini?
chrispepper1989
9
Bagaimana jika Anda ingin memberikan 0 sebagai argumen?
Julian Gold
1
Ini mengharuskan pengguna Anda untuk mengingat untuk menelepon dengan penghentian 0. Bagaimana cara ini lebih aman?
cp.engr
29

Dalam C ++ 0x yang luar biasa Anda bisa menggunakan templat variadic:

template <typename ... Ts>
void format_string(char *fmt, Ts ... ts) {}

template <typename ... Ts>
void debug_print(int dbg_lvl, char *fmt, Ts ... ts)
{
  format_string(fmt, ts...);
}
pengguna2023370
sumber
Jangan lupa bahwa templat variadic masih belum tersedia di Visual Studio ... ini tentu saja bukan urusan Anda!
Tom Swirly
1
Jika Anda menggunakan Visual Studio, templat variadic dapat ditambahkan ke Visual Studio 2012 menggunakan CTP November 2012. Jika Anda menggunakan Visual Studio 2013, Anda akan memiliki templat variadic.
user2023370
7

Anda dapat menggunakan unit inline untuk panggilan fungsi. (dalam kode ini saya menganggap argumen adalah karakter).

void format_string(char *fmt, ...);
void debug_print(int dbg_level, int numOfArgs, char *fmt, ...)
    {
        va_list argumentsToPass;
        va_start(argumentsToPass, fmt);
        char *list = new char[numOfArgs];
        for(int n = 0; n < numOfArgs; n++)
            list[n] = va_arg(argumentsToPass, char);
        va_end(argumentsToPass);
        for(int n = numOfArgs - 1; n >= 0; n--)
        {
            char next;
            next = list[n];
            __asm push next;
        }
        __asm push fmt;
        __asm call format_string;
        fprintf(stdout, fmt);
    }
Yoda
sumber
4
Tidak portabel, tergantung pada kompiler, dan mencegah optimasi kompiler. Solusi yang sangat buruk
Geoffroy
4
Baru tanpa hapus juga.
user7116
8
Setidaknya ini sebenarnya menjawab pertanyaan, tanpa mendefinisikan kembali pertanyaan.
lama12345
6

Anda dapat mencoba makro juga.

#define NONE    0x00
#define DBG     0x1F
#define INFO    0x0F
#define ERR     0x07
#define EMR     0x03
#define CRIT    0x01

#define DEBUG_LEVEL ERR

#define WHERESTR "[FILE : %s, FUNC : %s, LINE : %d]: "
#define WHEREARG __FILE__,__func__,__LINE__
#define DEBUG(...)  fprintf(stderr, __VA_ARGS__)
#define DEBUG_PRINT(X, _fmt, ...)  if((DEBUG_LEVEL & X) == X) \
                                      DEBUG(WHERESTR _fmt, WHEREARG,__VA_ARGS__)

int main()
{
    int x=10;
    DEBUG_PRINT(DBG, "i am x %d\n", x);
    return 0;
}
GeekyJ
sumber
6

Meskipun Anda dapat menyelesaikan masalah pemasangan formatter dengan menyimpannya di buffer lokal terlebih dahulu, tetapi itu perlu stack dan terkadang bisa menjadi masalah untuk ditangani. Saya mencoba mengikuti dan tampaknya berfungsi dengan baik.

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

void print(char const* fmt, ...)
{
    va_list arg;
    va_start(arg, fmt);
    vprintf(fmt, arg);
    va_end(arg);
}

void printFormatted(char const* fmt, va_list arg)
{
    vprintf(fmt, arg);
}

void showLog(int mdl, char const* type, ...)
{
    print("\nMDL: %d, TYPE: %s", mdl, type);

    va_list arg;
    va_start(arg, type);
    char const* fmt = va_arg(arg, char const*);
    printFormatted(fmt, arg);
    va_end(arg);
}

int main() 
{
    int x = 3, y = 6;
    showLog(1, "INF, ", "Value = %d, %d Looks Good! %s", x, y, "Infact Awesome!!");
    showLog(1, "ERR");
}

Semoga ini membantu.

VarunG
sumber
2

Solusi Ross sedikit beres-beres. Hanya berfungsi jika semua argumen adalah pointer. Juga implementasi bahasa harus mendukung penghapusan koma sebelumnya jika __VA_ARGS__kosong (baik Visual Studio C ++ dan GCC lakukan).

// pass number of arguments version
 #define callVardicMethodSafely(...) {value_t *args[] = {NULL, __VA_ARGS__}; _actualFunction(args+1,sizeof(args) / sizeof(*args) - 1);}


// NULL terminated array version
 #define callVardicMethodSafely(...) {value_t *args[] = {NULL, __VA_ARGS__, NULL}; _actualFunction(args+1);}
BSalita
sumber
0

Katakanlah Anda memiliki fungsi variadik khas yang Anda tulis. Karena setidaknya satu argumen diperlukan sebelum variadic ..., Anda harus selalu menulis argumen tambahan dalam penggunaan.

Atau apakah Anda

Jika Anda membungkus fungsi variadic Anda dalam makro, Anda tidak perlu arg sebelumnya. Pertimbangkan contoh ini:

#define LOGI(...)
    ((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))

Ini jelas jauh lebih nyaman, karena Anda tidak perlu menentukan argumen awal setiap kali.

Insinyur
sumber
-5

Saya tidak yakin apakah ini bekerja untuk semua kompiler, tetapi sejauh ini berhasil bagi saya.

void inner_func(int &i)
{
  va_list vars;
  va_start(vars, i);
  int j = va_arg(vars);
  va_end(vars); // Generally useless, but should be included.
}

void func(int i, ...)
{
  inner_func(i);
}

Anda dapat menambahkan ... ke inner_func () jika Anda mau, tetapi Anda tidak membutuhkannya. Ini berfungsi karena va_start menggunakan alamat variabel yang diberikan sebagai titik awal. Dalam hal ini, kami memberikan referensi ke variabel di func (). Jadi ia menggunakan alamat itu dan membaca variabel setelah itu di stack. Fungsi inner_func () membaca dari alamat stack dari func (). Jadi itu hanya berfungsi jika kedua fungsi menggunakan segmen stack yang sama.

Macro va_start dan va_arg umumnya akan berfungsi jika Anda memberi mereka var sebagai titik awal. Jadi jika mau, Anda bisa meneruskan pointer ke fungsi lain dan menggunakannya juga. Anda dapat membuat makro sendiri dengan cukup mudah. Semua makro lakukan adalah mengetikkan alamat memori. Namun membuat mereka bekerja untuk semua kompiler dan konvensi pemanggilan itu mengganggu. Jadi umumnya lebih mudah menggunakan yang datang dengan kompiler.

Jim
sumber