Вперед вызов функции с переменным числом аргументов в C



В C, можно ли переслать вызов функции с переменным числом аргументов? То есть,



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


пересылка вызова указанным выше способом, очевидно, не является строго необходимой в этом случае (поскольку вы можете регистрировать вызовы другими способами или использовать vfprintf), но кодовая база, над которой я работаю, требует, чтобы оболочка выполняла некоторую фактическую работу и не имела (и не могла добавить) вспомогательную функцию, подобную vfprintf.



[обновление: кажется, есть некоторая путаница, основанная на ответы, которые были предоставлены до сих пор. Чтобы сформулировать вопрос по-другому: В общем, вы можете обернуть некоторую произвольную вариативную функцию без изменения определения этой функции.]

546   10  

10 ответов:

Если у вас нет функции, аналогичной vfprintf что происходит va_list вместо переменного числа аргументов, ты не можешь этого сделать. См.http://c-faq.com/varargs/handoff.html.

пример:

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

не напрямую, однако это распространено (и вы найдете почти повсеместно случай в стандартной библиотеке) для вариативных функций, чтобы прийти в парах с varargs альтернативная функция стиля. например,printf/vprintf

The v... функции принимают параметр va_list, реализация которого часто выполняется с помощью компилятора конкретной 'macro magic', но вы гарантированно вызываете v... функция стиля из вариативной функции, как это будет работа:

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

это должно дать вам эффект, который вы ищете.

если вы рассматриваете возможность написания функции variadic library, вы также должны рассмотреть возможность создания компаньона стиля va_list, доступного как часть библиотеки. Как вы можете видеть из вашего вопроса, это может оказаться полезным для ваших пользователей.

C99 поддерживает макросы с аргументами вариативная; в зависимости от вашего компилятора, вы могли бы объявить макрос, который делает то, что вы хотите:

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

в целом, однако, лучшим решением является использование va_list форма функции, которую вы пытаетесь обернуть, если она существует.

почти, используя средства, доступные в <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;
}

обратите внимание, что вы должны использовать vprintf версия, а не обычная printf. Нет способа, чтобы напрямую вызвать функцию с переменным числом аргументов в этой ситуации без использования va_list.

поскольку на самом деле невозможно переадресовать такие вызовы хорошим способом, мы работали над этим, установив новый кадр стека с копией исходного кадра стека. Однако это очень непереносимо и делает все виды предположений, например, что код использует указатели кадров и "стандартные" соглашения о вызовах.

этот заголовочный файл позволяет обернуть вариативные функции для x86_64 и i386 (GCC). Он не работает для Аргументов с плавающей запятой, но должен быть прямо вперед, чтобы продлить для поддержки тех.

#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           , %%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

В конце концов, вы можете обернуть вызовы вроде этого:

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

использовать 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;
}

нет способа переадресовать такие вызовы функций, потому что единственное место, где вы можете получить необработанные элементы стека, находится в my_print(). Обычный способ обернуть такие вызовы-иметь две функции, одна из которых просто преобразует аргументы в различные varargs структуры, и другой, который фактически работает на этих структурах. Используя такую модель двойной функции, вы можете (например) обернуть printf() инициализация структуры в my_printf() С va_start(), а затем передать их в vfprintf().

извините за не по теме разглагольствования, но:

мета-проблема заключается в том, что интерфейс varargs в C был принципиально нарушен с самого начала. Это приглашение к переполнению буфера и недопустимому доступу к памяти, потому что конец списка аргументов не может быть найден без явного сигнала конца (который никто действительно не использует из лени). И он всегда полагался на эзотерические макросы, специфичные для реализации, с макросом vital va_copy (), поддерживаемым только на некоторые архитектур.

Да, вы можете сделать это, но это несколько некрасиво и вы должны знать максимальное количество аргументов. Кроме того, если вы находитесь на архитектуре, где аргументы не передаются в стеке, как x86 (например, PowerPC), вам нужно будет знать, есть ли "специальные" типы (double, floats, altivec и т. д.) используются, и если да, то справиться с ними соответственно. Это может быть болезненно быстро, но если вы находитесь на x86 или если исходная функция имеет четко определенный и ограниченный периметр, она может работать. It еще будет Хак, используйте его для отладки цели. Не создавайте программное обеспечение вокруг этого. В любом случае, вот рабочий пример на 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;
}

по какой-то причине вы не можете использовать поплавки с va_arg, gcc говорит, что они преобразуются в double, но программа аварийно завершает работу. Это само по себе демонстрирует, что это решение является взломом и что нет общего решения. В моем примере я предположил, что максимальное количество аргументов было 8, но вы можете увеличить это число. Обернутая функция также используются только целые числа, но он работает так же с другими "нормальными" параметрами, так как они всегда приводят к целым числам. Целевая функция будет знать их типы, но вашей промежуточной оболочке это не нужно. Оболочке также не нужно знать правильное количество аргументов, так как целевая функция также будет знать его. Чтобы сделать полезную работу (за исключением просто регистрации вызова), вам, вероятно, придется знать и то, и другое.

есть по существу три варианта.

один из них-не передавать его, но использовать вариативную реализацию вашей целевой функции и не передавать эллипсы. Другой - использовать вариативный макрос. Третий вариант-это все то, чего мне не хватает.

Я обычно иду с вариантом один, так как я чувствую, что это очень легко справиться. Второй вариант имеет недостаток, потому что есть некоторые ограничения на вызов переменных макросов.

вот пример код:

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

Comments

    Ничего не найдено.