Weiterleiten eines Aufrufs einer Variadic-Funktion in C

Lesezeit: 8 Minuten

Patricks Benutzeravatar
Patrick

Ist es in C möglich, den Aufruf einer variadischen Funktion weiterzuleiten? Wie in,

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

Das Weiterleiten des Aufrufs auf die obige Weise ist in diesem Fall offensichtlich nicht unbedingt erforderlich (da Sie Aufrufe auf andere Weise protokollieren oder vfprintf verwenden könnten), aber die Codebasis, an der ich arbeite, erfordert, dass der Wrapper tatsächlich etwas tut, und tut dies auch Ich habe keine Hilfsfunktion ähnlich wie vfprintf (und kann sie auch nicht hinzugefügt haben).

[Update: there seems to be some confusion based on the answers that have been supplied so far. To phrase the question another way: in general, can you wrap some arbitrary variadic function without modifying that function’s definition.]

  • tolle Frage – glücklicherweise kann ich eine vFunc-Überladung hinzufügen, die eine va_list benötigt. Danke fürs Schreiben.

    – Gishu

    3. Juni 2009 um 7:16 Uhr

Benutzeravatar von Adam Rosenfield
Adam Rosenfeld

Wenn Sie keine Funktion analog zu haben vfprintf das dauert ein va_list statt einer variablen Anzahl von Argumenten, du kannst es nicht. Sehen http://c-faq.com/varargs/handoff.html.

Beispiel:

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

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

    – Philipp

    11. Januar 2015 um 2:03 Uhr

  • Mindestens ein Argument muss übergeben werden, myfun("") wird nicht unterstützt – gibt es eine Lösung, wie ich das beheben kann? (MSVC druckt: zu wenige Argumente für Aufruf)

    – mvorisek

    20. Juli 2020 um 17:28 Uhr

  • @mvorisek: Nein, dafür gibt es keine genau definierte Lösung, da variadische Funktionen mindestens einen benannten Parameter vor dem nehmen müssen .... Ihre Problemumgehung scheint möglicherweise mit Ihrem speziellen Compiler zu funktionieren, aber es ist immer noch undefiniertes Verhalten gemäß dem Sprachstandard.

    – Adam Rosenfield

    21. Juli 2020 um 15:13 Uhr

  • Archivierter Link da der alte unter Link Fäulnis litt

    – Seeseemelk

    6. Juli 2021 um 11:11 Uhr

Benutzeravatar von CB Bailey
CB Bailey

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

Die v…-Funktionen nehmen einen va_list-Parameter, dessen Implementierung oft mit Compiler-spezifischer “Makro-Magie” erfolgt, aber Sie können sicher sein, dass das Aufrufen der v…-Style-Funktion aus einer variadischen Funktion wie dieser funktioniert:

#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 erwägen, eine variadische Bibliotheksfunktion zu schreiben, sollten Sie auch erwägen, einen va_list-Stilbegleiter als Teil der Bibliothek verfügbar zu machen. Wie Sie Ihrer Frage entnehmen können, kann es sich für Ihre Benutzer als nützlich erweisen.

  • Ich bin mir ziemlich sicher, dass Sie anrufen müssen va_copy vor dem Passieren der myargs Variable zu einer anderen Funktion. Bitte sehen MSC39-Cwo es besagt, dass das, was Sie tun, undefiniertes Verhalten ist.

    – Avigiano

    26. Februar 2016 um 4:23 Uhr


  • @aviggiano: Die Regel lautet “Nicht anrufen va_arg() auf einen va_list das einen unbestimmten Wert hat”, mache ich nicht, weil ich es nie benutze va_arg in meiner aufrufenden Funktion. Das Wert von myargs nach dem Anruf (in diesem Fall) an vprintf ist unbestimmt (vorausgesetzt, es tut uns va_arg). Das sagt die Norm myargs „wird an die weitergegeben va_end Makro vor jedem weiteren Verweis auf [it]”; Das ist genau das, was ich tue. Ich muss diese Argumente nicht kopieren, weil ich nicht die Absicht habe, sie in der aufrufenden Funktion zu durchlaufen.

    – CB Bailey

    26. Februar 2016 um 15:16 Uhr


  • Sie haben Recht. Das Linux-Mann Seite bestätigt das. Wenn ap wird an eine Funktion übergeben, die verwendet va_arg(ap,type) dann der Wert von ap ist nach der Rückkehr dieser Funktion undefiniert.

    – Avigiano

    26. Februar 2016 um 16:28 Uhr


Benutzeravatar von Commodore Jaeger
Commodore Jäger

C99 unterstützt Makros mit variadischen Argumenten; Abhängig von Ihrem Compiler können Sie möglicherweise ein Makro deklarieren, das 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 die beste Lösung jedoch die Verwendung von va_liste Form der Funktion, die Sie umschließen möchten, falls eine vorhanden ist.

Da es nicht wirklich möglich ist, solche Anrufe auf nette Weise weiterzuleiten, haben wir dies umgangen, indem wir einen neuen Stapelrahmen mit einer Kopie des ursprünglichen Stapelrahmens eingerichtet haben. Dies ist jedoch höchst untragbar und macht alle möglichen AnnahmenzB dass der Code Frame-Zeiger und die ‘Standard’-Aufrufkonventionen verwendet.

Diese Header-Datei ermöglicht es, verschiedene Funktionen für x86_64 und i386 (GCC) zu umschließen. Es funktioniert nicht für Gleitkommaargumente, sollte aber einfach zu erweitern sein, 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 folgt umbrechen:

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

Fast, mit den verfügbaren 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 verwenden müssen vprintf Version statt einfach printf. Es gibt keine Möglichkeit, eine variadische Funktion in dieser Situation direkt aufzurufen, ohne zu verwenden va_list.

  • Danke, aber von der Frage: “Die Codebasis, an der ich arbeite […] hat keine Hilfsfunktion ähnlich wie vfprintf (und kann sie auch nicht hinzugefügt haben).

    – Patrick

    29. September 2008 um 20:53 Uhr

Benutzeravatar von Nate Eldredge
Nate Eldredge

gcc bietet eine Erweiterung an, die dies tun kann: __builtin_apply und Verwandte. Sehen Aufbau von Funktionsaufrufen im gcc-Handbuch.

Ein Beispiel:

#include <stdio.h>

int my_printf(const char *fmt, ...) {
    void *args = __builtin_apply_args();
    printf("Hello there! Format string is %s\n", fmt);
    void *ret = __builtin_apply((void (*)())printf, args, 1000);
    __builtin_return(ret);
}

int main(void) {
    my_printf("%d %f %s\n", -37, 3.1415, "spam");
    return 0;
}

Probieren Sie es auf Godbolt aus

Es gibt einige Vorsichtshinweise in der Dokumentation, dass es in komplizierteren Situationen möglicherweise nicht funktioniert. Und Sie müssen eine maximale Größe für die Argumente fest codieren (hier habe ich 1000 verwendet). Aber es könnte eine vernünftige Alternative zu den anderen Ansätzen sein, bei denen der Stack entweder in C oder in der Assemblersprache zerlegt wird.

  • Danke, aber von der Frage: “Die Codebasis, an der ich arbeite […] hat keine Hilfsfunktion ähnlich wie vfprintf (und kann sie auch nicht hinzugefügt haben).

    – Patrick

    29. September 2008 um 20:53 Uhr

Benutzeravatar von Greg Rogers
Greg Rogers

Verwenden Sie 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;
}

  • Danke, aber von der Frage: “Die Codebasis, an der ich arbeite […] hat keine Hilfsfunktion ähnlich wie vfprintf (und kann sie auch nicht hinzugefügt haben).

    – Patrick

    29. September 2008 um 20:53 Uhr

1426030cookie-checkWeiterleiten eines Aufrufs einer Variadic-Funktion in C

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy