it-swarm.com.de

Weiterleiten eines Aufrufs einer variadischen Funktion in C

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 in der oben beschriebenen 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 einige tatsächliche Arbeiten ausführt, und nicht habe (und kann nicht hinzugefügt haben) eine Hilfsfunktion ähnlich wie vfprintf.

[Update: Es scheint einige Verwirrung zu geben, die auf den bisherigen Antworten beruht. Um die Frage anders auszudrücken: Können Sie im Allgemeinen eine beliebige Variadic-Funktion einschließen? ohne die Definition dieser Funktion zu ändern.]

170
Patrick

Wenn Sie keine Funktion analog zu vfprintf haben, die ein va_list Anstelle einer variablen Anzahl von Argumenten können Sie dies nicht tun . Siehe http://c-faq.com/varargs/handoff.html .

Beispiel:

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

Nicht direkt, aber es ist üblich (und Sie werden es in der Standardbibliothek fast überall finden), dass verschiedene Funktionen paarweise mit einer alternativen Funktion im varargs -Stil angeboten werden. z.B. printf/vprintf

Die v ... -Funktionen verwenden einen va_list -Parameter, dessen Implementierung häufig mit compilerspezifischer 'Makromagie' durchgeführt wird. Sie können jedoch sicher sein, dass das Aufrufen der v ... -Stilfunktion aus einer variablen Funktion wie der folgenden 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 überlegen, eine variable Bibliotheksfunktion zu schreiben, sollten Sie auch in Betracht ziehen, einen Begleiter im Stil von va_list als Teil der Bibliothek zur Verfügung zu stellen. Wie Sie anhand Ihrer Frage sehen können, kann dies für Ihre Benutzer hilfreich sein.

56
CB Bailey

C99 unterstützt Makros mit unterschiedlichen 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 es jedoch die beste Lösung, die Form va_list der Funktion zu verwenden, die Sie umbrechen möchten, falls eine vorhanden ist.

44

Fast unter Verwendung der in <stdarg.h> Verfügbaren Funktionen:

#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 Version vprintf anstelle von printf verwenden müssen. In dieser Situation gibt es keine Möglichkeit, eine Variadic-Funktion direkt aufzurufen, ohne va_list Zu verwenden.

11
Greg Hewgill

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. Dies ist jedoch sehr unportabel und macht alle Arten von Annahmen, z. dass der Code Frame-Zeiger und die "Standard" -Aufrufkonventionen verwendet.

Diese Header-Datei ermöglicht das Umbrechen verschiedener Funktionen für x86_64 und i386 (GCC). Es funktioniert nicht für Gleitkomma-Argumente, sollte aber einfach erweitert werden können, 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 verpacken:

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

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;
}
3
Greg Rogers

Es gibt keine Möglichkeit, solche Funktionsaufrufe weiterzuleiten, da der einzige Speicherort, an dem Sie unformatierte Stapelelemente abrufen können, in my_print() liegt. Der übliche Weg, solche Aufrufe umzubrechen, besteht darin, zwei Funktionen zu haben, eine, die nur die Argumente in die verschiedenen varargs -Strukturen konvertiert, und eine andere, die diese Strukturen tatsächlich bearbeitet. Mit einem solchen Doppelfunktionsmodell können Sie beispielsweise printf() umbrechen, indem Sie die Strukturen in my_printf() mit va_start() initialisieren und sie dann an vfprintf().

2
John Millikin

Ja, Sie können es tun, aber es ist etwas hässlich und Sie müssen die maximale Anzahl von Argumenten kennen. Wenn Sie sich in einer Architektur befinden, in der die Argumente nicht wie beim x86 (z. B. PowerPC) auf dem Stack übergeben werden, müssen Sie außerdem wissen, ob "spezielle" Typen (double, floats, altivec usw.) verwendet werden und ob Behandle sie dementsprechend. Es kann schnell schmerzhaft sein, aber wenn Sie mit x86 arbeiten oder wenn die ursprüngliche Funktion einen genau definierten und begrenzten Umfang hat, kann es funktionieren. Es wird immer noch ein Hack sein, benutze es zum Debuggen. Bauen Sie Ihre Software nicht darum herum. Hier ist ein funktionierendes Beispiel für 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 können Sie keine floats mit va_arg verwenden, gcc sagt, sie werden in double konvertiert, aber das Programm stürzt ab. Das allein zeigt, dass diese Lösung ein Hack ist und es keine generelle Lösung gibt. In meinem Beispiel habe ich angenommen, dass die maximale Anzahl von Argumenten 8 ist, aber Sie können diese Anzahl erhöhen. Die umschlossene Funktion verwendete auch nur Ganzzahlen, funktioniert aber mit anderen "normalen" Parametern genauso, da sie immer in Ganzzahlen umgewandelt werden. Die Zielfunktion kennt ihre Typen, Ihr Intermediary Wrapper muss dies jedoch nicht. Der Wrapper muss auch nicht die richtige Anzahl von Argumenten kennen, da die Zielfunktion diese auch kennt. Um nützliche Arbeit zu leisten (außer den Anruf zu protokollieren), müssen Sie wahrscheinlich beide kennen.

1
user10146

Entschuldigung für die Off-Topic-Beschimpfung, aber:

Das Meta-Problem ist, dass die varargs-Schnittstelle in C von Anfang an grundlegend beschädigt wurde. Es ist eine Aufforderung, Überläufe und ungültige Speicherzugriffe zwischenzuspeichern, da das Ende der Argumentliste nicht ohne ein explizites Endsignal gefunden werden kann (das niemand wirklich aus Faulheit nutzt). Und es stützte sich immer auf esoterische implementierungsspezifische Makros, wobei das wichtige Makro va_copy () nur auf einigen Architekturen unterstützt wurde.

1
Thorsten79

Grundsätzlich gibt es drei Möglichkeiten.

Eine besteht darin, sie nicht weiterzugeben, sondern die variable Implementierung Ihrer Zielfunktion zu verwenden und die Ellipsen nicht weiterzugeben. Die andere ist die Verwendung eines variablen Makros. Die dritte Option ist alles, was mir fehlt.

Normalerweise entscheide ich mich für Option eins, da ich der Meinung bin, dass dies sehr einfach zu handhaben ist. Option zwei hat einen Nachteil, da es einige Einschränkungen beim Aufrufen verschiedener Makros gibt.

Hier ist ein Beispielcode:

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