En C, est-il possible de transmettre l'invocation d'une fonction variadique? Un péché,
int my_printf(char *fmt, ...) {
fprintf(stderr, "Calling printf with fmt %s", fmt);
return SOMEHOW_INVOKE_LIBC_PRINTF;
}
Le transfert de l'invocation de la manière ci-dessus n'est évidemment pas absolument nécessaire dans ce cas (car vous pouvez enregistrer les invocations d'une autre manière, ou utiliser vfprintf), mais la base de code sur laquelle je travaille nécessite que l'encapsuleur effectue un travail réel. n'a pas (et ne peut pas avoir ajouté) de fonction d'assistance semblable à vfprintf.
[Mise à jour: il semble y avoir une certaine confusion d'après les réponses fournies jusqu'à présent. Pour formuler la question autrement: en général, pouvez-vous envelopper une fonction variadique arbitraire sans modifier la définition de cette fonction.]
Si vous n'avez pas de fonction analogue à vfprintf
, cela prend un va_list
au lieu d'un nombre variable d'arguments, , vous ne pouvez pas le faire . Voir http://c-faq.com/varargs/handoff.html .
Exemple:
void myfun(const char *fmt, va_list argp) {
vfprintf(stderr, fmt, argp);
}
Ce n’est pas directement, mais il est courant (et vous trouverez presque universellement le cas dans la bibliothèque standard) que les fonctions variadiques viennent par paires avec une fonction alternative de style varargs
. par exemple. printf
/vprintf
Les fonctions v ... prennent un paramètre va_list, dont l'implémentation est souvent effectuée avec une "magie des macros" spécifique au compilateur, mais vous avez la garantie que l'appel de la fonction de style v ... à partir d'une fonction variadique comme celle-ci fonctionnera:
#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;
}
Cela devrait vous donner l'effet que vous recherchez.
Si vous envisagez d'écrire une fonction de bibliothèque variadique, vous devriez également envisager de rendre un compagnon de style va_list disponible dans le cadre de la bibliothèque. Comme vous pouvez le voir d'après votre question, cela peut s'avérer utile pour vos utilisateurs.
C99 prend en charge macros avec arguments variadiques ; En fonction de votre compilateur, vous pourrez peut-être déclarer une macro qui fait ce que vous voulez:
#define my_printf(format, ...) \
do { \
fprintf(stderr, "Calling printf with fmt %s\n", format); \
some_other_variadac_function(format, ##__VA_ARGS__); \
} while(0)
En général, cependant, la meilleure solution consiste à utiliser la forme va_list de la fonction que vous essayez de boucler, le cas échéant.
Presque, en utilisant les installations disponibles dans <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;
}
Notez que vous devrez utiliser la version vprintf
plutôt que plain printf
. Il n'y a pas moyen d'appeler directement une fonction variadique dans cette situation sans utiliser va_list
.
Comme il n’est pas vraiment possible de transférer de tels appels de manière agréable, nous avons résolu ce problème en créant un nouveau cadre de pile avec une copie du cadre de pile original. Cependant, il s’agit extrêmement instable et faisant toutes sortes d’hypothèses, par ex. que le code utilise des pointeurs de cadre et les conventions d’appel "standard".
Ce fichier d’entête permet d’emballer des fonctions variadiques pour x86_64 et i386 (GCC). Cela ne fonctionne pas pour les arguments à virgule flottante, mais devrait être simple à étendre pour les supporter.
#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
En fin de compte, vous pouvez encapsuler des appels comme ceci:
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;
}
Utilisez 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;
}
Il n'y a aucun moyen de transférer de tels appels de fonction car le seul emplacement où vous pouvez récupérer des éléments de pile bruts est dans my_print()
. La méthode habituelle pour encapsuler de tels appels consiste à avoir deux fonctions, une qui convertit simplement les arguments en différentes structures varargs
, et une autre qui opère réellement sur ces structures. En utilisant un tel modèle à double fonction, vous pouvez (par exemple) encapsuler printf()
en initialisant les structures dans my_printf()
avec va_start()
, puis les transmettre à vfprintf()
.
Oui, vous pouvez le faire, mais c'est un peu moche et vous devez connaître le nombre maximal d'arguments. De plus, si vous êtes sur une architecture où les arguments ne sont pas passés sur la pile comme le x86 (par exemple, PowerPC), vous devrez savoir si des types "spéciaux" (double, floats, altivec, etc.) sont utilisés et si alors, traitez-les en conséquence. Cela peut être douloureux rapidement, mais si vous êtes sur x86 ou si la fonction d'origine a un périmètre bien défini et limité, cela peut fonctionner. Ce sera toujours un hack, utilisez-le à des fins de débogage. Ne construisez pas votre logiciel autour de cela. Quoi qu'il en soit, voici un exemple de travail sur 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;
}
Pour une raison quelconque, vous ne pouvez pas utiliser de float avec va_arg, gcc dit qu'ils sont convertis en double mais le programme se bloque. Cela seul démontre que cette solution est un hack et qu'il n'y a pas de solution générale. Dans mon exemple, j'ai supposé que le nombre maximal d'arguments était de 8, mais vous pouvez augmenter ce nombre. La fonction encapsulée utilise également uniquement des entiers, mais fonctionne de la même manière avec d'autres paramètres "normaux" puisqu'ils sont toujours convertis en entiers. La fonction cible connaîtra leurs types mais votre wrapper intermédiaire n'en aura pas besoin. Le wrapper n'a pas non plus besoin de connaître le bon nombre d'arguments puisque la fonction cible le saura également. Pour faire un travail utile (sauf simplement enregistrer l’appel), vous devrez probablement connaître les deux.
Désolé pour le coup de gueule hors sujet, mais:
Le méta-problème est que l'interface varargs en C a été fondamentalement cassée depuis le tout début. C'est une invitation aux dépassements de mémoire tampon et aux accès mémoire non valides, car la fin de la liste d'arguments ne peut être trouvée sans un signal de fin explicite (que personne n'utilise vraiment par paresse). Et il s’appuyait toujours sur des macros ésotériques spécifiques à l’implémentation, la macro vitale va_copy () étant uniquement prise en charge sur les architectures certaines.
Il y a essentiellement trois options.
L’une consiste à ne pas le transmettre, mais à utiliser l’implémentation variadique de votre fonction cible et à ne pas transmettre les ellipses. L'autre consiste à utiliser une macro variadique. La troisième option est tout ce qui me manque.
Je vais habituellement avec l'option un, car je sens que c'est vraiment facile à manipuler. L'option deux présente un inconvénient car il existe certaines limitations à l'appel de macros variadiques.
Voici un exemple de 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");
}