Değişken sayıda argümanın aktarılması


333

Değişken sayıda bağımsız değişken alan bir C işlevim olduğunu varsayalım: İlk işleve gelen tüm bağımsız değişkenleri geçirerek değişken sayıda bağımsız değişken bekleyen başka bir işlevi nasıl arayabilirim?

Misal:

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);
 }

4
Örneğin, format_string () ve fprintf () 'ye fmt ilettiğiniz için örneğiniz bana biraz garip görünüyor. Format_string () bir şekilde yeni bir dize döndürmeli mi?
Kristopher Johnson

2
Örnek mantıklı değil. Sadece kodun ana hatlarını göstermekti.
Vicent Marti

162
"googled olmalı": Katılıyorum. Google'da çok fazla gürültü var (belirsiz, çoğu zaman kafa karıştırıcı bilgiler). Stackoverflow iyi (oy, kabul edilen cevap) olması gerçekten yardımcı olur!
Ansgar

71
Sadece tartmak için: Google'dan bu soruya geldim ve yığın taşması olduğu için cevabın yararlı olacağından oldukça emindi. Öyleyse sor!
tenpn

32
@Ilya: Hiç kimse Google'ın dışında bir şey yazmamış olsaydı, Google'da aranacak hiçbir bilgi olmazdı.
Erik Kaplun

Yanıtlar:


211

Elipsleri iletmek için bunları bir va_list'e dönüştürmeniz ve bu va_list'i ikinci fonksiyonunuzda kullanmanız gerekir. özellikle;

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);
}

3
Kod sorudan alınır ve gerçekten sadece işlevsel bir şeyden ziyade elipslerin nasıl dönüştürüleceğinin bir örneğidir. Eğer bakarsanız, format_stringfmt için yerinde değişiklikler yapmak zorunda kalacağından, pek de yararlı olmayacaktır. Seçenekler, format_string öğesinden tamamen kurtulmayı ve vfprintf kullanmayı içerir, ancak bu, format_string öğesinin gerçekte ne yaptığına ilişkin varsayımlar yapar veya format_string öğesinin farklı bir dize döndürmesini içerir. Sonuncuyu göstermek için cevabı düzenleyeceğim.
SmacL

1
Biçim dizeniz printf ile aynı biçim dizesi komutlarını kullanıyorsa, biçim dizeniz iletilen gerçek bağımsız değişkenlerle uyumlu değilse size uyarı vermek için gcc ve clang gibi bazı derleyiciler de alabilirsiniz. GCC işlev özniteliğine bakın. biçiminde "biçimlendirin: gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html .
Doug Richardson

1
Argleri üst üste iki kez geçiriyorsanız bu işe yaramıyor gibi görünüyor.
fotanus

2
@fotanus: ile bir işlev çağırırsanız argptrve çağrılan işlev hiç kullanırsa argptr, yapılacak tek güvenli şey çağırmak va_end()ve yeniden başlatmak va_start(argptr, fmt);için yeniden başlatmaktır. Veya va_copy()sisteminiz destekliyorsa kullanabilirsiniz (C99 ve C11 gerektirir; C89 / 90 desteklemedi).
Jonathan Leffler

1
@ ThomasPadron-McCarthy'nin yorumunun artık güncel olmadığını ve son fprintf'in iyi olduğunu lütfen unutmayın.
Frederick

59

Yaramaz ve taşınabilir olmayan numaralara girmek istemediğiniz sürece, ona kaç argüman ilettiğinizi bilmeden printf'i çağırmanın bir yolu yoktur.

Genel olarak kullanılan solüsyon yüzden, her zaman vararg fonksiyonlarının alternatif bir biçimini sunmaktır printfsahip vprintfbir aldığı va_listyerine .... ...Versiyonları etrafına toplanmıştır va_listsürümleri.


Not vsyslogolduğu değil POSIX uyumlu.
patryk.beza

53

Değişkin Fonksiyonlar olabilir tehlikeli . İşte daha güvenli bir numara:

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

func((type[]){val1,val2,val3,val4,0});

11
Daha da iyisi bu numara: #define callVardicMethodSafely(values...) ({ values *v = { values }; _actualFunction(values, sizeof(v) / sizeof(*v)); })
Richard J. Ross III

5
@ RichardJ.RossIII Yorumunuzu genişletmek isterdim, neredeyse okunabilir değil, kodun arkasındaki fikri çıkaramıyorum ve aslında çok ilginç ve kullanışlı görünüyor.
penelope

5
@ArtOfWarfare onun kötü bir kesmek olduğunu kabul emin değilim, Rose harika bir çözüm var ama func ((tip []) {val1, val2, 0}); bu da tıknaz hissediyor, eğer #define func_short_cut (...) func ((type []) { VA_ARGS }) varsa wheras ; o zaman func_short_cut (1, 2, 3, 4, 0); hangi Rose'un düzgün hilesi ek yarar ile normal bir varyasyon fonksiyonu ile aynı sözdizimi verir ... burada sorun nedir?
chrispepper1989

9
0'ı argüman olarak geçmek isterseniz ne olur?
Julian Gold

1
Bu, kullanıcılarınızın sonlandırma 0 ile aramayı hatırlamalarını gerektirir. Nasıl daha güvenlidir?
cp.engr

29

Muhteşem C ++ 0x'de varyasyon şablonları kullanabilirsiniz:

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...);
}

Varyasyon şablonlarının hala Visual Studio'da mevcut olmadığını unutmayın ... bu elbette sizi ilgilendirmeyebilir!
Tom Swirly

1
Visual Studio kullanıyorsanız, Kasım 2012 CTP kullanılarak Visual Studio 2012'ye varyasyon şablonları eklenebilir. Visual Studio 2013 kullanıyorsanız, değişken şablonlarınız olacaktır.
user2023370

7

İşlev çağrısı için satır içi montajı kullanabilirsiniz. (bu kodda argümanların karakter olduğunu varsayıyorum).

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);
    }

4
Taşınabilir değildir, derleyiciye bağlıdır ve derleyici optimizasyonunu engeller. Çok kötü bir çözüm.
Geoffroy

4
Silmeden de yeni.
user7116

8
En azından bu aslında soruyu yeniden tanımlamadan soruyu cevaplar.
lama12345

6

Makroyu da deneyebilirsiniz.

#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;
}

6

Biçimlendiriciyi önce yerel arabellekte saklayarak geçmeyi çözebilirsiniz, ancak bu yığına ihtiyaç duyar ve bazen başa çıkmak için sorun olabilir. Takip etmeyi denedim ve iyi çalışıyor gibi görünüyor.

#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");
}

Bu yardımcı olur umarım.


2

Ross'un çözümü biraz temizlendi. Yalnızca tüm argümanlar işaretçi ise çalışır. Ayrıca dil uygulaması __VA_ARGS__boşsa önceki virgülün elenmesini desteklemelidir (hem Visual Studio C ++ hem de GCC bunu yapar).

// 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);}

0

Diyelim ki, yazdığınız tipik bir varyasyon fonksiyonunuz var. Variadic olandan önce en az bir bağımsız değişken gerektiğinden ..., kullanımda her zaman fazladan bir bağımsız değişken yazmanız gerekir.

Yoksa sen mi?

Variadic işlevinizi bir makroya sarırsanız, önceki bağımsız değişkene ihtiyacınız yoktur. Bu örneği düşünün:

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

Her seferinde ilk argümanı belirtmeniz gerekmediği için bu çok daha uygundur.


-5

Bunun tüm derleyiciler için işe yarayıp yaramadığından emin değilim, ama benim için şimdiye kadar çalıştı.

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);
}

İsterseniz inner_func () öğesine ... ekleyebilirsiniz, ancak buna ihtiyacınız yoktur. Va_start, verilen değişkenin adresini başlangıç ​​noktası olarak kullandığı için çalışır. Bu durumda, func () içindeki bir değişkene bir referans veriyoruz. Böylece bu adresi kullanır ve bundan sonra değişkenleri yığında okur. İnner_func () işlevi func () 'in yığın adresinden okuyor. Bu nedenle, yalnızca her iki işlev de aynı yığın segmentini kullanıyorsa çalışır.

Va_start ve va_arg makroları, başlangıç ​​noktası olarak herhangi bir değişken verirseniz genellikle çalışır. İsterseniz işaretçileri diğer işlevlere geçirebilir ve bunları da kullanabilirsiniz. Kendi makrolarınızı kolayca yapabilirsiniz. Tüm makrolar tipik bellek adresleridir. Ancak onları tüm derleyiciler ve çağrışım kuralları için çalışmak sinir bozucu. Bu nedenle derleyici ile birlikte gelenleri kullanmak genellikle daha kolaydır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.