当前位置:  开发笔记 > 前端 > 正文

在C中转发可变参数函数的调用

如何解决《在C中转发可变参数函数的调用》经验,为你挑选了5个好方法。

在C中,是否可以转发可变参数函数的调用?如,

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

在这种情况下,以上述方式转发调用显然不是必需的(因为你可以用其他方式记录调用,或者使用vfprintf),但是我正在处理的代码库要求包装器做一些实际的工作,并且没有没有(并且不能添加)类似于vfprintf的辅助函数.

[更新:基于迄今为止提供的答案,似乎存在一些混淆.用另一种方式表达问题:通常,你可以包装一些任意的可变参数函数而不修改该函数的定义.



1> Adam Rosenfi..:

如果你不具备的功能类似vfprintf,需要一个va_list替代的参数个数可变,你不能做到这一点.看到http://c-faq.com/varargs/handoff.html.

例:

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


根据问题的重要性,内联汇编的调用仍然提供了这种可能性.

2> CB Bailey..:

不是直接的,但是变量函数与varargs样式替代函数成对出现是常见的(并且在标准库中几乎普遍存在这种情况).例如printf/vprintf

v ...函数采用va_list参数,其实现通常使用编译器特定的"宏魔法"来完成,但是你可以保证从这样的可变参数函数调用v ...样式函数将起作用:

#include 

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

这应该会给你你想要的效果.

如果您正在考虑编写可变参数库函数,您还应该考虑将va_list样式伴随作为库的一部分提供.从您的问题中可以看出,它可以证明对您的用户有用.


@aviggiano:规则是"不要在具有不确定值的`va_list`上调用`va_arg()`",我不这样做,因为我从不在调用函数中使用`va_arg`.调用(在本例中)到`vprintf`之后``myargs`的_value_是_indeterminate_(假设它为我们`va_arg`).标准说"myargs`"应该在进一步引用[it]之前传递给`va_end`宏; 这正是我的工作.我不需要复制那个args,因为我无意在调用函数中迭代它们.

3> Commodore Ja..:

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形式(如果存在).



4> Greg Hewgill..:

几乎使用以下设施:

#include 
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.



5> coltox..:

由于不可能以一种很好的方式转发这样的调用,我们通过设置一个带有原始堆栈帧副本的新堆栈帧来解决这个问题.然而,这是非常不可移植的并且做出各种假设,例如代码使用帧指针和"标准"调用约定.

此头文件允许包装x86_64和i386(GCC)的可变参数函数.它不适用于浮点参数,但应该直接扩展以支持这些参数.

#ifndef _VA_ARGS_WRAPPER_H
#define _VA_ARGS_WRAPPER_H
#include 
#include 
#include 
#include 
#include 

/* 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

最后你可以像这样包装调用:

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

推荐阅读
linjiabin43
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有