una todas tipos retorno parametros lenguaje las funciones funcion estructura ejemplos con c variadic

todas - Reenviar una invocación de una función variadica en C



tipos de funciones en c (10)

C99 admite macros con argumentos variados ; Dependiendo de su compilador, es posible que pueda declarar una macro que hace lo que desea:

#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 la forma va_list de la función que está tratando de envolver, en caso de que exista.

En C, ¿es posible reenviar la invocación de una función variadica? 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 puede registrar invocaciones de otras maneras, o usar vfprintf), pero la base de código en la que estoy trabajando requiere que el contenedor haga un trabajo real, y no lo hace No tiene (ni puede haber agregado) una función auxiliar similar a vfprintf.

[Actualización: parece haber cierta confusión basada en las respuestas que se han proporcionado hasta ahora. Para formular la pregunta de otra manera: en general, ¿puedes ajustar alguna función variable arbitraria sin modificar la definición de esa función ?]


Casi, usando 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 necesitará usar la versión de vprintf lugar de printf simple. No hay una forma de llamar directamente a una función variadica en esta situación sin usar va_list .


Como en realidad no es posible reenviar dichas llamadas de una manera agradable, trabajamos alrededor de esto configurando un nuevo marco de pila con una copia del marco de pila original. Sin embargo, esto es muy poco práctico y hace todo tipo de suposiciones , por ejemplo, que el código utiliza punteros de trama y las convenciones de llamadas "estándar".

Este archivo de encabezado permite ajustar funciones variadic para x86_64 e i386 (GCC). No funciona para argumentos de coma flotante, pero debería ser sencillo de ampliar para apoyarlos.

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


Hay esencialmente tres opciones.

Una es no pasarla, sino usar la implementación variadica de su función objetivo y no pasar las elipsis. El otro es usar una macro variadica. La tercera opción es todo lo que me falta.

Usualmente voy con la opción uno ya que siento que esto es realmente fácil de manejar. La opción dos tiene un inconveniente porque existen algunas limitaciones para invocar macros variadic.

Aquí hay un código de ejemplo:

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


No directamente, sin embargo, es común (y encontrará casi universalmente el caso en la biblioteca estándar) para las funciones variadic venir en pares con una función alternativa de estilo varargs . por ejemplo, printf / vprintf

Las funciones v ... toman un parámetro va_list, cuya implementación a menudo se hace con macro magia específica del compilador, pero se garantiza que llamar a la función v ... style desde una función variadica 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 darle el efecto que está buscando.

Si está considerando escribir una función de biblioteca variadic, también debería considerar la posibilidad de hacer que un acompañante de estilo va_list esté disponible como parte de la biblioteca. Como puede ver en su pregunta, puede ser útil para sus usuarios.


No hay forma de reenviar estas llamadas a función porque la única ubicación donde puede recuperar elementos de pila sin procesar se encuentra en my_print() . La forma habitual de ajustar llamadas como esa es tener dos funciones, una que simplemente convierta los argumentos en las diversas estructuras varargs , y otra que realmente opere sobre esas estructuras. Utilizando dicho modelo de doble función, puede (por ejemplo) ajustar printf() inicializando las estructuras en my_printf() con va_start() , y luego pasarlas a vfprintf() .


Perdón por la diatriba fuera del tema, pero:

El metaproblema es que la interfaz varargs en C se ha roto fundamentalmente desde el principio. Es una invitación a desbordamientos de búfer y accesos de memoria no válidos porque el final de la lista de argumentos no se puede encontrar sin una señal final explícita (que nadie utiliza realmente por pereza). Y siempre se basó en macros esotéricas específicas de implementación, con la macro vital va_copy () solo soportada en algunas arquitecturas.


Sí, puedes hacerlo, pero es algo feo y debes saber la cantidad máxima de argumentos. Además, si está en una arquitectura donde los argumentos no se transmiten en la pila como el x86 (por ejemplo, PowerPC), deberá saber si se usan tipos "especiales" (dobles, flotantes, altivec, etc.) y si por lo tanto, lidiar con ellos en consecuencia. Puede ser doloroso rápidamente, pero si está en x86 o si la función original tiene un perímetro bien definido y limitado, puede funcionar. Todavía será un truco , úselo para la depuración. No construyas tu software alrededor de eso. De todos modos, aquí hay un ejemplo de trabajo en 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; }

Por alguna razón, no puede usar flotantes con va_arg, gcc dice que se convierten en dobles pero el programa falla. Solo eso demuestra que esta solución es un truco y que no hay una solución general. En mi ejemplo, asumí que el número máximo de argumentos era 8, pero puede aumentar ese número. La función envolvente también solo usa números enteros, pero funciona de la misma manera con otros parámetros "normales" ya que siempre emiten números enteros. La función objetivo conocerá sus tipos, pero su envoltura intermedia no necesita hacerlo. El contenedor tampoco necesita saber la cantidad correcta de argumentos, ya que la función objetivo también lo sabrá. Para hacer un trabajo útil (excepto simplemente para registrar la llamada), probablemente tendrá que saber ambos.


Si no tiene una función análoga a vfprintf que tome una va_list lugar de una cantidad variable de argumentos, no puede hacerlo . Ver .

Ejemplo:

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


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