2008-09-29 3 views
143

In C, ist es möglich, den Aufruf einer Variadic-Funktion weiterzuleiten? Wie in,Weiterleiten eines Aufrufs einer Variadic-Funktion in C

int my_printf(char *fmt, ...) { 
    fprintf(stderr, "Calling printf with fmt %s", fmt); 
    return SOMEHOW_INVOKE_LIBC_PRINTF; 
} 

Forwarding den Aufruf in der oben beschriebenen Weise offensichtlich nicht unbedingt notwendig in diesem Fall ist (da Sie Anrufungen auf andere Weise einloggen konnten, oder vfprintf verwenden), aber die Code-Basis arbeite ich an erfordert Der Wrapper, um einige tatsächliche Arbeit zu tun, und hat nicht eine Hilfsfunktion ähnlich wie vfprintf (und konnte nicht hinzugefügt haben).

[Update: Es scheint einige Verwirrung basierend auf den Antworten, die bisher geliefert wurden. Begriff die Frage eine andere Art und Weise. Im Allgemeinen, Sie einige beliebige variadische Funktion ohne diese Funktion Definition zu modifizieren wickeln kann]

+1

ehrfürchtige Frage - zum Glück kann ich eine vFunc Überlastung hinzufügen, die eine va_list nimmt. Danke fürs Schreiben. – Gishu

Antwort

122

Wenn Sie nicht über eine Funktion analog zu vfprintf haben, die eine va_list statt einer variablen Anzahl von Argumenten entgegennimmt, Sie können es nicht. Siehe http://c-faq.com/varargs/handoff.html.

Beispiel:

void myfun(const char *fmt, va_list argp) { 
    vfprintf(stderr, fmt, argp); 
} 
+0

Danke, beantwortet meine Frage perfekt. – Patrick

+2

Je nachdem, wie wichtig das Problem ist, bietet der Aufruf durch Inline-Assemblierung diese Möglichkeit. – daluege

3

Verwendung vfprintf:

int my_printf(char *fmt, ...) { 
    va_list va; 
    int ret; 

    va_start(va, fmt); 
    ret = vfprintf(stderr, fmt, va); 
    va_end(va); 
    return ret; 
} 
+1

Danke, aber von der Frage: "Die Codebasis, an der ich arbeite, [...] hat keine Hilfsfunktion, die vfprintf ähnlich ist (und hätte sie nicht hinzufügen können)." – Patrick

10

Fast, mit den vorhandenen Einrichtungen in <stdarg.h>:

#include <stdarg.h> 
int my_printf(char *format, ...) 
{ 
    va_list args; 
    va_start(args, format); 
    int r = vprintf(format, args); 
    va_end(args); 
    return r; 
} 

Beachten Sie, dass Sie die vprintf-Version lieber als printf verwenden müssen. Es gibt keine Möglichkeit, eine Variadic-Funktion in dieser Situation direkt aufzurufen, ohne va_list zu verwenden.

+1

Danke, aber von der Frage: "Die Codebasis, an der ich arbeite, [...] hat keine Hilfsfunktion, die vfprintf ähnlich ist (und hätte nicht hinzugefügt werden können)." – Patrick

2

Es gibt keine Möglichkeit, solche Funktionsaufrufe weiterzuleiten, da der einzige Speicherort für Raw-Stack-Elemente in my_print() liegt. Der übliche Weg, um solche Aufrufe zu umbrechen, besteht darin, zwei Funktionen zu haben, eine, die die Argumente in die verschiedenen varargs Strukturen umwandelt, und eine andere, die tatsächlich auf diesen Strukturen operiert. Unter Verwendung eines solchen Doppelfunktionsmodells können Sie (zum Beispiel) printf() umbrechen, indem Sie die Strukturen in my_printf() mit va_start() initialisieren und dann an vfprintf() übergeben.

+0

So gibt es keine Möglichkeit, den Aufruf weiterzuleiten, wenn Sie die Implementierung der Funktion, die Sie umbrechen möchten, nicht ändern können? – Patrick

+0

Rechts. Am besten drücken Sie, um einen vprintf-artigen Wrapper hinzuzufügen. –

44

Nicht direkt, aber ist es üblich (und Sie werden fast überall den Fall in der Standardbibliothek finden) für variadische Funktionen mit einer varargs Stil paarweise kommen alternative Funktion. z.B. printf/vprintf

Die v ... Funktionen einen va_list Parameter übernehmen, wird die Umsetzung, die oft mit Compiler spezifischen ‚Makro Magie‘ gemacht, aber Sie werden garantiert, dass die v ... Stil-Funktion aus einer variadische Funktion aufrufen So funktioniert das:

#include <stdarg.h> 

int m_printf(char *fmt, ...) 
{ 
    int ret; 

    /* Declare a va_list type variable */ 
    va_list myargs; 

    /* Initialise the va_list variable with the ... after fmt */ 

    va_start(myargs, fmt); 

    /* Forward the '...' to vprintf */ 
    ret = vprintf(fmt, myargs); 

    /* Clean up the va_list */ 
    va_end(myargs); 

    return ret; 
} 

Dies sollte Ihnen den Effekt geben, den Sie suchen.

Wenn Sie eine variadische Bibliotheksfunktion schreiben möchten, sollten Sie auch einen va_list-Stil als Teil der Bibliothek verfügbar machen. Wie Sie Ihrer Frage entnehmen können, kann es für Ihre Benutzer nützlich sein.

+0

Ich bin mir ziemlich sicher, dass Sie 'va_copy' aufrufen müssen, bevor Sie die Variable' myargs' an eine andere Funktion übergeben. Siehe [MSC39-C] (https://www.securecoding.cert.org/confluence/display/c/MSC39-C.+Do+not+call+va_arg() + auf + a + va_list + das + hat + an + unbestimmter + Wert), wo angegeben wird, dass das, was Sie tun, ein undefiniertes Verhalten ist. – aviggiano

+1

@aviggiano: Die Regel lautet: "Nennen Sie' va_arg() 'nicht auf einer' va_list', die einen unbestimmten Wert hat ", tue ich das nicht, weil ich nie' va_arg' in meiner Aufruffunktion verwende. Der _value_ von 'myargs' nach dem Aufruf (in diesem Fall) zu' vprintf' ist _indeterminate_ (angenommen, dass es uns 'va_arg' tut). Der Standard besagt, dass "Myargs" vor jeder weiteren Bezugnahme auf "es" in das Makro "va_end" übernommen werden soll. Genau das mache ich. Ich brauche diese Argumente nicht zu kopieren, weil ich nicht die Absicht habe, sie in der Aufruffunktion zu durchlaufen. –

+0

Sie haben Recht. Die [Linux man] (http://linux.die.net/man/3/va_arg) Seite bestätigt das. _Wenn 'ap' an eine Funktion übergeben wird, die' va_arg (ap, type) 'verwendet, dann ist der Wert von' ap' nach der Rückgabe dieser Funktion nicht definiert._ – aviggiano

39

C99 unterstützt macros with variadic arguments; auf Ihrem Compiler abhängig, könnte der Lage sein, ein Makro zu erklären, das tut, was Sie wollen:

#define my_printf(format, ...) \ 
    do { \ 
     fprintf(stderr, "Calling printf with fmt %s\n", format); \ 
     some_other_variadac_function(format, ##__VA_ARGS__); \ 
    } while(0) 

Im Allgemeinen ist jedoch die beste Lösung, um die va_list Form der Funktion zu Sie versuchen, verwenden wickeln, sollte einer existieren.

1

Sorry für den Off-Topic-rant, aber:

Das Meta-Problem ist, dass die varargs Schnittstelle in C von Anfang an grundsätzlich gebrochen wurde. Es ist eine Einladung zu Pufferüberläufen und ungültigen Speicherzugriffen, weil das Ende der Argumentliste nicht ohne ein explizites Endsignal gefunden werden kann (das niemand wirklich aus Faulheit benutzt). Und es beruhte immer auf esoterischen implementierungsspezifischen Makros, wobei das wichtige va_copy() Makro nur auf einigen Architekturen unterstützt wurde.

+0

Es ist eher ein Kludn, aber es ist in Fairness zu K & R, eins Ich muss anmerken, dass es wahrscheinlich besser ist als alles andere, was zu der Zeit verfügbar war. Haben irgendwelche anderen Sprachen irgendeine Art von effizienter variadischer Funktionsunterstützung überhaupt zur Verfügung gestellt? Meine einzigen wirklich großen Beschwerden sind der Mangel an konsistenter va_copy-Verfügbarkeit und das Fehlen der offensichtlichen Unterstützung für allgemeine vxprintf (senden Sie jedes Zeichen an eine angegebene Funktion). Ich habe einige Beschwerden mit der C-Sprache (z. B. ganzzahlige Vergleichsregeln), aber nicht so sehr mit va_args. – supercat

1

Ja, Sie können es tun, aber es ist etwas hässlich und Sie haben die maximale Anzahl von Argumenten kennen. Wenn Sie sich in einer Architektur befinden, in der die Argumente nicht wie der x86 (zB PowerPC) auf dem Stack übergeben werden, müssen Sie wissen, ob "spezielle" Typen (double, floats, altivec usw.) verwendet werden und ob Behandle sie dementsprechend. Es kann schnell schmerzhaft sein, aber wenn Sie auf x86 sind oder wenn die ursprüngliche Funktion einen gut definierten und begrenzten Umfang hat, kann es funktionieren. Es wird noch ein Hack sein, verwenden Sie es Zweck für das Debuggen. Baue dir dafür keine Software. Wie auch immer, hier ist ein funktionierendes Beispiel auf x86:

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

int old_variadic_function(int n, ...) 
{ 
    va_list args; 
    int i = 0; 

    va_start(args, n); 

    if(i++<n) printf("arg %d is 0x%x\n", i, va_arg(args, int)); 
    if(i++<n) printf("arg %d is %g\n", i, va_arg(args, double)); 
    if(i++<n) printf("arg %d is %g\n", i, va_arg(args, double)); 

    va_end(args); 

    return n; 
} 

int old_variadic_function_wrapper(int n, ...) 
{ 
    va_list args; 
    int a1; 
    int a2; 
    int a3; 
    int a4; 
    int a5; 
    int a6; 
    int a7; 
    int a8; 

    /* Do some work, possibly with another va_list to access arguments */ 

    /* Work done */ 

    va_start(args, n); 

    a1 = va_arg(args, int); 
    a2 = va_arg(args, int); 
    a3 = va_arg(args, int); 
    a4 = va_arg(args, int); 
    a5 = va_arg(args, int); 
    a6 = va_arg(args, int); 
    a7 = va_arg(args, int); 

    va_end(args); 

    return old_variadic_function(n, a1, a2, a3, a4, a5, a6, a7, a8); 
} 

int main(void) 
{ 
    printf("Call 1: 1, 0x123\n"); 
    old_variadic_function(1, 0x123); 
    printf("Call 2: 2, 0x456, 1.234\n"); 
    old_variadic_function(2, 0x456, 1.234); 
    printf("Call 3: 3, 0x456, 4.456, 7.789\n"); 
    old_variadic_function(3, 0x456, 4.456, 7.789); 
    printf("Wrapped call 1: 1, 0x123\n"); 
    old_variadic_function_wrapper(1, 0x123); 
    printf("Wrapped call 2: 2, 0x456, 1.234\n"); 
    old_variadic_function_wrapper(2, 0x456, 1.234); 
    printf("Wrapped call 3: 3, 0x456, 4.456, 7.789\n"); 
    old_variadic_function_wrapper(3, 0x456, 4.456, 7.789); 

    return 0; 
} 

Aus irgendeinem Grund kann man nicht schwimmt mit va_arg verwenden, gcc, sagt sie, aber die Programmabstürze sind umgewandelt zu verdoppeln. Das allein zeigt, dass diese Lösung ein Hack ist und dass es keine allgemeine Lösung gibt. In meinem Beispiel nahm ich an, dass die maximale Anzahl von Argumenten 8 war, aber Sie können diese Zahl erhöhen. Die umbrochene Funktion verwendet ebenfalls nur ganze Zahlen, aber sie funktioniert auf die gleiche Weise mit anderen 'normalen' Parametern, da sie immer in Ganzzahlen umgewandelt werden. Die Zielfunktion kennt ihre Typen, aber Ihr intermediärer Wrapper muss dies nicht tun. Der Wrapper muss auch nicht die richtige Anzahl von Argumenten kennen, da die Zielfunktion sie auch kennt. Um nützliche Arbeit zu tun (außer nur den Anruf zu protokollieren), müssen Sie wahrscheinlich beide wissen.

9

Da es nicht wirklich möglich ist, solche Anrufe auf eine nette Art zu übermitteln, arbeiteten wir um diese durch einen neuen Stapelrahmen mit einer Kopie des Originalstapelrahmen einrichten. Jedoch ist dies hoch unportabel und macht alle Arten von Annahmen, z.B. dass der Code Frame-Zeiger und die 'Standard' Aufrufkonventionen verwendet.

Diese Header-Datei ermöglicht variadische Funktionen für x86_64 und i386 (GCC) zu wickeln. Es funktioniert nicht für Fließkomma-Argumente, sollte aber direkt erweitert werden, um diese zu unterstützen.

#ifndef _VA_ARGS_WRAPPER_H 
#define _VA_ARGS_WRAPPER_H 
#include <limits.h> 
#include <stdint.h> 
#include <alloca.h> 
#include <inttypes.h> 
#include <string.h> 

/* This macros allow wrapping variadic functions. 
* Currently we don't care about floating point arguments and 
* we assume that the standard calling conventions are used. 
* 
* The wrapper function has to start with VA_WRAP_PROLOGUE() 
* and the original function can be called by 
* VA_WRAP_CALL(function, ret), whereas the return value will 
* be stored in ret. The caller has to provide ret 
* even if the original function was returning void. 
*/ 

#define __VA_WRAP_CALL_FUNC __attribute__ ((noinline)) 

#define VA_WRAP_CALL_COMMON()          \ 
    uintptr_t va_wrap_this_bp,va_wrap_old_bp;      \ 
    va_wrap_this_bp = va_wrap_get_bp();        \ 
    va_wrap_old_bp = *(uintptr_t *) va_wrap_this_bp;    \ 
    va_wrap_this_bp += 2 * sizeof(uintptr_t);      \ 
    size_t volatile va_wrap_size = va_wrap_old_bp - va_wrap_this_bp; \ 
    uintptr_t *va_wrap_stack = alloca(va_wrap_size);     \ 
    memcpy((void *) va_wrap_stack,         \ 
     (void *)(va_wrap_this_bp), va_wrap_size); 


#if (__WORDSIZE == 64) 

/* System V AMD64 AB calling convention */ 

static inline uintptr_t __attribute__((always_inline)) 
va_wrap_get_bp() 
{ 
    uintptr_t ret; 
    asm volatile ("mov %%rbp, %0":"=r"(ret)); 
    return ret; 
} 


#define VA_WRAP_PROLOGUE()   \ 
    uintptr_t va_wrap_ret;   \ 
    uintptr_t va_wrap_saved_args[7]; \ 
    asm volatile (     \ 
    "mov %%rsi,  (%%rax)\n\t"  \ 
    "mov %%rdi, 0x8(%%rax)\n\t"  \ 
    "mov %%rdx, 0x10(%%rax)\n\t"  \ 
    "mov %%rcx, 0x18(%%rax)\n\t"  \ 
    "mov %%r8, 0x20(%%rax)\n\t"  \ 
    "mov %%r9, 0x28(%%rax)\n\t"  \ 
    :        \ 
    :"a"(va_wrap_saved_args)   \ 
    ); 

#define VA_WRAP_CALL(func, ret)   \ 
    VA_WRAP_CALL_COMMON();     \ 
    va_wrap_saved_args[6] = (uintptr_t)va_wrap_stack; \ 
    asm volatile (      \ 
    "mov  (%%rax), %%rsi \n\t"   \ 
    "mov 0x8(%%rax), %%rdi \n\t"   \ 
    "mov 0x10(%%rax), %%rdx \n\t"   \ 
    "mov 0x18(%%rax), %%rcx \n\t"   \ 
    "mov 0x20(%%rax), %%r8 \n\t"   \ 
    "mov 0x28(%%rax), %%r9 \n\t"   \ 
    "mov   $0, %%rax \n\t"   \ 
    "call    *%%rbx \n\t"   \ 
    : "=a" (va_wrap_ret)     \ 
    : "b" (func), "a" (va_wrap_saved_args) \ 
    : "%rcx", "%rdx",      \ 
     "%rsi", "%rdi", "%r8", "%r9",  \ 
     "%r10", "%r11", "%r12", "%r14",  \ 
     "%r15"        \ 
    );          \ 
    ret = (typeof(ret)) va_wrap_ret; 

#else 

/* x86 stdcall */ 

static inline uintptr_t __attribute__((always_inline)) 
va_wrap_get_bp() 
{ 
    uintptr_t ret; 
    asm volatile ("mov %%ebp, %0":"=a"(ret)); 
    return ret; 
} 

#define VA_WRAP_PROLOGUE() \ 
    uintptr_t va_wrap_ret; 

#define VA_WRAP_CALL(func, ret)  \ 
    VA_WRAP_CALL_COMMON();    \ 
    asm volatile (     \ 
    "mov %2, %%esp \n\t"   \ 
    "call *%1  \n\t"   \ 
    : "=a"(va_wrap_ret)    \ 
    : "r" (func),      \ 
     "r"(va_wrap_stack)    \ 
    : "%ebx", "%ecx", "%edx" \ 
    );         \ 
    ret = (typeof(ret))va_wrap_ret; 
#endif 

#endif 

Am Ende können Sie Anrufe wie diese wickeln:

int __VA_WRAP_CALL_FUNC wrap_printf(char *str, ...) 
{ 
    VA_WRAP_PROLOGUE(); 
    int ret; 
    VA_WRAP_CALL(printf, ret); 
    printf("printf returned with %d \n", ret); 
    return ret; 
} 
0

Im Wesentlichen gibt es drei Möglichkeiten.

Man ist nicht weiter zu geben, sondern die variadische Umsetzung Ihrer Zielfunktion zu verwenden und nicht auf den Ellipsen passieren. Der andere ist ein variadisches Makro. Die dritte Option ist all die Dinge, die ich vermisse.

Ich gehe normalerweise mit Option eins, da ich das Gefühl habe, dass dies wirklich einfach zu handhaben ist. Option zwei hat einen Nachteil, da beim Aufruf variadischer Makros einige Einschränkungen bestehen.

Hier einige Beispiel-Code:

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

#define Option_VariadicMacro(f, ...)\ 
    printf("printing using format: %s", f);\ 
    printf(f, __VA_ARGS__) 

int Option_ResolveVariadicAndPassOn(const char * f, ...) 
{ 
    int r; 
    va_list args; 

    printf("printing using format: %s", f); 
    va_start(args, f); 
    r = vprintf(f, args); 
    va_end(args); 
    return r; 
} 

void main() 
{ 
    const char * f = "%s %s %s\n"; 
    const char * a = "One"; 
    const char * b = "Two"; 
    const char * c = "Three"; 
    printf("---- Normal Print ----\n"); 
    printf(f, a, b, c); 
    printf("\n"); 
    printf("---- Option_VariadicMacro ----\n"); 
    Option_VariadicMacro(f, a, b, c); 
    printf("\n"); 
    printf("---- Option_ResolveVariadicAndPassOn ----\n"); 
    Option_ResolveVariadicAndPassOn(f, a, b, c); 
    printf("\n"); 
} 
Verwandte Themen