如何用可变长度参数包装函数?

时间:2008-09-03 10:12:28

标签: c++ c variadic-functions

我希望用C / C ++来做这件事。

我遇到Variable Length Arguments,但这表明Python和& C使用libffi

现在,如果我想用printf

包裹myprintf函数

我的工作如下:

void myprintf(char* fmt, ...)
{
    va_list args;
    va_start(args,fmt);
    printf(fmt,args);
    va_end(args);
}

int _tmain(int argc, _TCHAR* argv[])
{
    int a = 9;
    int b = 10;
    char v = 'C';
    myprintf("This is a number: %d and \nthis is a character: %c and \n another number: %d\n",a, v, b);
    return 0;
}

但结果并不像预期的那样!

This is a number: 1244780 and
this is a character: h and
another number: 29953463

我想念的任何地方?

7 个答案:

答案 0 :(得分:65)

问题是你不能将'printf'与va_args一起使用。如果使用可变参数列表,则必须使用 vprintf 。 vprint,vsprintf,vfprintf等(Microsoft的C运行时中还有'安全'版本可以防止缓冲区溢出等)。

您的示例如下:

void myprintf(char* fmt, ...)
{
    va_list args;
    va_start(args,fmt);
    vprintf(fmt,args);
    va_end(args);
}

int _tmain(int argc, _TCHAR* argv[])
{
    int a = 9;
    int b = 10;
    char v = 'C'; 
    myprintf("This is a number: %d and \nthis is a character: %c and \n another number: %d\n",a, v, b);
    return 0;
}

答案 1 :(得分:11)

在C ++ 11中,这是一个使用Variadic templates的可能解决方案:

template<typename... Args>
void myprintf(const char* fmt, Args... args )
{
    std::printf( fmt, args... ) ;
}

修改

正如@rubenvb指出需要考虑的权衡,例如,您将为每个实例生成代码,这将导致代码膨胀。

答案 2 :(得分:8)

我也不确定你的意思是纯粹的

在C ++中我们使用

#include <cstdarg>
#include <cstdio>

class Foo
{   void Write(const char* pMsg, ...);
};

void Foo::Write( const char* pMsg, ...)
{
    char buffer[4096];
    std::va_list arg;
    va_start(arg, pMsg);
    std::vsnprintf(buffer, 4096, pMsg, arg);
    va_end(arg);
    ...
}

答案 3 :(得分:4)

实际上,有一种方法可以从包装器中调用没有va_list版本的函数。想法是使用汇编程序,不要在堆栈中触摸参数,并临时替换函数返回地址。

Visual C x86的示例。 call addr_printf来电printf()

__declspec( thread ) static void* _tls_ret;

static void __stdcall saveret(void *retaddr) {
    _tls_ret = retaddr;
}

static void* __stdcall _getret() {
    return _tls_ret;
}

__declspec(naked)
static void __stdcall restret_and_return_int(int retval) {
    __asm {
        call _getret
        mov [esp], eax   ; /* replace current retaddr with saved */
        mov eax, [esp+4] ; /* retval */
        ret 4
    }
}

static void __stdcall _dbg_printf_beg(const char *fmt, va_list args) {
    printf("calling printf(\"%s\")\n", fmt);
}

static void __stdcall _dbg_printf_end(int ret) {
    printf("printf() returned %d\n", ret);
}

__declspec(naked)
int dbg_printf(const char *fmt, ...)
{
    static const void *addr_printf = printf;
    /* prolog */
    __asm {
        push ebp
        mov  ebp, esp
        sub  esp, __LOCAL_SIZE
        nop
    }
    {
        va_list args;
        va_start(args, fmt);
        _dbg_printf_beg(fmt, args);
        va_end(args);
    }
    /* epilog */
    __asm {
        mov  esp, ebp
        pop  ebp
    }
    __asm  {
        call saveret
        call addr_printf
        push eax
        push eax
        call _dbg_printf_end
        call restret_and_return_int
    }
}

答案 4 :(得分:1)

您使用的是C还是C ++?下一个C ++版本C ++ 0x将支持variadic templates,它提供了解决该问题的方法。

可以通过聪明的运算符重载来实现另一种解决方法,以实现如下语法:

void f(varargs va) {
    BOOST_FOREACH(varargs::iterator i, va)
        cout << *i << " ";
}

f(args = 1, 2, 3, "Hello");

为了使其工作,必须实现类varargs以覆盖返回代理对象的operator =,而代理对象又会覆盖operator ,。但是,据我所知,在当前的C ++中使这种变体类型安全是不可能的,因为它必须通过类型擦除来工作。

答案 5 :(得分:0)

您如何理解纯C / C ++解决方案?

在C运行时跨平台支持rest参数(...)。

http://msdn.microsoft.com/en-us/library/kb57fad8.aspx

答案 6 :(得分:0)

void myprintf(char* fmt, ...)
{
    va_ list args;
    va_ start(args,fmt);
    printf(fmt,args); ----> This is the fault. vprintf(fmt, args); should have been used.
    va_ end(args);
}
If you're just trying to call printf, 
there's a printf variant called vprintf that takes 
the va_list directly :  vprintf(fmt, args);