2008-10-15 13 views
290

Powiedzmy, że mam funkcję C, która pobiera zmienną liczbę argumentów: Jak wywołać inną funkcję, która oczekuje zmiennej liczby argumentów z jej wnętrza, przekazując wszystkie argumenty, które dostały się do pierwszej funkcjonować?Przekazywanie zmiennej liczby argumentów wokół

Przykład:

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

Twój przykład wygląda na nieco dziwny, ponieważ przekazujesz fmt zarówno do format_string(), jak i do fprintf(). Czy format_string() powinien jakoś zwrócić nowy łańcuch? –

+2

Przykład nie ma sensu. Chodziło o pokazanie zarysu kodu. –

+139

"powinno się googlować": nie zgadzam się. Google ma dużo hałasu (niejasne, często mylące informacje). Posiadanie dobrego (głosowanie w górę, zaakceptowana odpowiedź) na stackoverflow naprawdę pomaga! – Ansgar

Odpowiedz

179

zdać elipsy na temat, trzeba je konwertować do va_list i użyć tego va_list w drugiej funkcji. Konkretnie;

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

Piszę ten komentarz jako rodzaj ogłoszenia o bezpieczeństwie publicznym. Jeden z moich uczniów wprawił mnie w zakłopotanie wywołaniem fprintf na końcu i pomyślał, że nie musisz wysyłać argptr jako argumentu. Żeby było jeszcze gorzej, to faktycznie zadziałało, ponieważ czasem zdarza się niezdefiniowane zachowanie. Tak więc, proszę zauważyć, że jak napisał Vinent Marti w komentarzu do jego pytania, ostatnie połączenie z fprintf nie ma sensu! –

+3

Kod pochodzi z pytania i jest tylko ilustracją tego, jak przekonwertować elipsy, a nie jakikolwiek funkcjonalny. Jeśli spojrzysz na to, 'format_string' również nie będzie użyteczne, ponieważ musiałoby dokonać modyfikacji in-situ do fmt, co z pewnością nie powinno być zrobione. Opcje obejmowałyby całkowite pozbycie się funkcji format_string i użycie vfprintf, ale to sprawia, że ​​założenia dotyczące tego, co faktycznie robi format_string, lub mają format_śledzić zwracają inny ciąg znaków. Będę edytować odpowiedź, aby pokazać to drugie. –

+1

Jeśli twój ciąg formatowy korzysta z tego samego formatu poleceń formatujących, co printf, możesz także uzyskać kompilatory takie jak gcc i clang, aby dać ci ostrzeżenia, jeśli twój ciąg formatu nie jest zgodny z rzeczywistymi argumentami przekazanymi. Zobacz atrybut funkcji GCC "format" po więcej szczegółów: http://gc.gnu.org/onlinedocs/gcc/Function-Attributes.html. –

48

Nie ma sposobu wywoływania (EG) printf, nie wiedząc, jak wiele argumentów jesteś przechodząc do niego, chyba że chcesz dostać się niegrzeczne i non-przenośnych sztuczek.

Powszechnie stosowanym rozwiązaniem jest, aby zawsze zapewnić alternatywną postać funkcji vararg tak printf ma vprintf która przyjmuje va_list zamiast .... Wersje ... są tylko owijkami wokół wersji va_list.

+0

Należy zauważyć, że ['vsyslog'] (http://linux.die.net/man/3/vsyslog) to ** nie ** [POSIX] (http://pubs.opengroup.org/onlinepubs/9699919799/) zgodny. –

28

W wspaniałej C++ 0x można użyć zmiennej liczbie argumentów szablony:

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

Nie zapominaj, że szablony variadic nadal nie są dostępne w Visual Studio ... to oczywiście nie powinno Cię martwić! –

+1

Jeśli używasz programu Visual Studio, szablony variadic można dodać do programu Visual Studio 2012 za pomocą CTP z listopada 2012. Jeśli używasz Visual Studio 2013, będziesz miał szablony variadic. – user2023370

5

Można użyć inline zespół do wywołania funkcji. (w tym kodzie zakładam, że argumenty są znakami).

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

Nie przenośny, zależy od kompilatora i uniemożliwia optymalizację kompilatora. Bardzo złe rozwiązanie. – Geoffroy

+4

Nowe bez usuwania. – user7116

+4

Przynajmniej to faktycznie odpowiada na pytanie, bez ponownego definiowania pytania. – lama12345

51

Variadic Functions może być dangerous. A oto bezpieczniejsza sztuczka:

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

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

Jeszcze lepsza jest ta sztuczka: '#define callVardicMethodSafely (wartości ...) ({value * v = {values}; _actualFunction (wartości, sizeof (v)/sizeof (* v));}) ' –

+3

@ RichardJ.RossIII Życzę sobie, aby rozwinąć swój komentarz, jest to mało czytelne, Nie mogę zrozumieć idei kryjącej się za kodem i wygląda on naprawdę interesująco i użytecznie. – penelope

+0

@penelope zasadniczo tworzy tablicę z wartości przekazywanych do makra. Może działać tylko na GCC, ale nie wiem na pewno. –

1

Roztwór Rossa nieco oczyszczony. Działa tylko wtedy, gdy wszystkie argumenty są wskaźnikami. Również implementacja języka musi obsługiwać usuwanie poprzedniego przecinka, jeśli __VA_ARGS__ jest pusty (zarówno Visual Studio C++, jak i GCC do).

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

Nie jestem pewien, czy to działa dla wszystkich kompilatorów, ale jak dotąd zadziałało to dla mnie.

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

Możesz dodać ... do inner_func(), jeśli chcesz, ale go nie potrzebujesz. Działa, ponieważ va_start używa adresu danej zmiennej jako punktu początkowego. W tym przypadku podajemy odwołanie do zmiennej w func(). Więc używa tego adresu i czyta zmienne po tym na stosie. Funkcja inner_func() czyta z adresu stosu func(). Działa to tylko wtedy, gdy obie funkcje używają tego samego segmentu stosu.

Makra va_start i va_arg będą działać, jeśli jako punkt wyjścia nadamy im dowolny var. Jeśli chcesz, możesz przekazać wskaźniki innym funkcjom i użyć ich również. Możesz łatwo tworzyć własne makra. Wszystkie makra są typowymi adresami pamięci. Jednak tworzenie ich dla wszystkich kompilatorów i konwencji wywoływania jest denerwujące. Więc generalnie łatwiej jest używać tych, które pochodzą z kompilatora.

1

Możesz także wypróbować makro.

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

Załóżmy, że masz typową funkcję wariadyczną, którą napisałeś. Ponieważ co najmniej jeden argument jest wymagany przed wersjowym ..., zawsze musisz napisać dodatkowy argument w użyciu.

A może Ty?

Jeśli otoczysz funkcję wariadyczną w makrze, nie potrzebujesz poprzedzającego argumentu. Rozważmy następujący przykład:

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

To jest oczywiście o wiele bardziej wygodne, ponieważ nie musisz określić Pierwszym argumentem za każdym razem.

Powiązane problemy