Reenviar una invocación de una función variádica en C

8 minutos de lectura

avatar de usuario
Patricio

En C, ¿es posible reenviar la invocación de una función variádica? Como en,

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

Reenviar la invocación de la manera anterior, obviamente, no es estrictamente necesario en este caso (ya que podría registrar las invocaciones de otras maneras, o usar vfprintf), pero el código base en el que estoy trabajando requiere que el contenedor haga algún trabajo real, y no No tiene (y no puede haber agregado) una función auxiliar similar a vfprintf.

[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.]

  • pregunta increíble: afortunadamente, puedo agregar una sobrecarga de vFunc que toma una lista va_list. Gracias por publicar.

    – Gishu

    3 de junio de 2009 a las 7:16

avatar de usuario
adam rosenfield

Si no tienes una función análoga a vfprintf eso toma un va_list en lugar de un número variable de argumentos, no puedes hacerlo. Ver http://c-faq.com/varargs/handoff.html.

Ejemplo:

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

  • Dependiendo de cuán crucial sea el problema, la invocación por ensamblado en línea aún ofrece esta posibilidad.

    – filip

    11 de enero de 2015 a las 2:03

  • Se debe pasar al menos un argumento, myfun("") no es compatible, ¿hay alguna solución para solucionarlo? (Imprime MSVC: muy pocos argumentos para la llamada)

    – mvorisek

    20 de julio de 2020 a las 17:28

  • @mvorisek: No, no hay una solución bien definida para eso, ya que se requiere que las funciones variádicas tomen al menos un parámetro con nombre antes del .... Su solución puede parecer que funciona con su compilador en particular, pero sigue siendo un Comportamiento indefinido de acuerdo con el estándar del idioma.

    – Adam Rosenfield

    21 de julio de 2020 a las 15:13

  • Enlace archivado como el anterior sufría de link rot

    – Seeseemelk

    6 de julio de 2021 a las 11:11

avatar de usuario
CB Bailey

No directamente, sin embargo, es común (y encontrará casi universalmente el caso en la biblioteca estándar) que las funciones variádicas vengan en pares con un varargs Función alternativa de estilo. p.ej printf/vprintf

Las funciones v… toman un parámetro va_list, cuya implementación a menudo se realiza con ‘macromacro’ específica del compilador, pero tiene la garantía de que llamar a la función de estilo v… desde una función variable como esta funcionará:

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

Esto debería darte el efecto que estás buscando.

Si está considerando escribir una función de biblioteca variádica, también debe considerar hacer que un complemento de estilo va_list esté disponible como parte de la biblioteca. Como puede ver en su pregunta, puede resultar útil para sus usuarios.

  • Estoy bastante seguro de que necesitas llamar va_copy antes de pasar el myargs variable a otra función. Por favor mira MSC39-Cdonde indica que lo que estás haciendo es un comportamiento indefinido.

    – aviggiano

    26 de febrero de 2016 a las 4:23


  • @aviggiano: La regla es “No llamar va_arg() en un va_list que tiene un valor indeterminado”, no hago eso porque nunca uso va_arg en mi función de llamada. Él valor de myargs después de la llamada (en este caso) a vprintf es indeterminado (suponiendo que nos haga va_arg). La norma dice que myargs “se pasará a la va_end macro antes de cualquier otra referencia a [it]”; que es exactamente lo que hago. No necesito copiar esos argumentos porque no tengo intención de iterar a través de ellos en la función de llamada.

    –CB Bailey

    26 de febrero de 2016 a las 15:16


  • Tienes razón. Él hombre linux La página lo confirma. Si ap se pasa a una función que utiliza va_arg(ap,type) entonces el valor de ap no está definido después del retorno de esa función.

    – aviggiano

    26 de febrero de 2016 a las 16:28


avatar de usuario
Comodoro Jaeger

C99 apoya macros con argumentos variados; dependiendo de su compilador, es posible que pueda declarar una macro que haga lo que quiere:

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

En general, sin embargo, la mejor solución es usar el va_list forma de la función que está tratando de envolver, en caso de que exista.

Casi, utilizando las instalaciones disponibles en <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;
}

Tenga en cuenta que tendrá que utilizar el vprintf versión en lugar de simple printf. No hay forma de llamar directamente a una función variádica en esta situación sin usar va_list.

Como no es realmente posible reenviar tales llamadas de una manera agradable, solucionamos esto configurando un nuevo marco de pila con una copia del marco de pila original. Sin embargo esto es altamente inportable y hace todo tipo de suposicionespor ejemplo, que el código utiliza punteros de cuadro y las convenciones de llamada ‘estándar’.

Este archivo de encabezado permite envolver funciones variadas para x86_64 e i386 (GCC). No funciona para argumentos de punto flotante, pero debería ser sencillo de extender para admitirlos.

#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

Al final, puede envolver llamadas como esta:

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

avatar de usuario
Nate Eldredge

gcc ofrece una extensión que puede hacer esto: __builtin_apply y familiares Ver Construcción de llamadas a funciones en el manual de gcc.

Un ejemplo:

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

Pruébalo en Godbolt

Hay algunas precauciones en la documentación de que podría no funcionar en situaciones más complicadas. Y debe codificar un tamaño máximo para los argumentos (aquí usé 1000). Pero podría ser una alternativa razonable a los otros enfoques que implican diseccionar la pila en lenguaje C o ensamblador.

avatar de usuario
greg rogers

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

  • Gracias, pero a partir de la pregunta: “el código base en el que estoy trabajando […] no tiene (y no puede haber agregado) una función auxiliar similar a vfprintf”.

    – Patricio

    29 de septiembre de 2008 a las 20:53

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad