2008-09-03 57 views
43

我期待在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 

任何一点我错过了什么?

+2

对于这个问题的答案是现在_very_不同的是C++ 11超出。 – 2013-03-04 22:01:52

+0

@MooingDuck的确,我添加了一个`Variadic templates`答案,你认为在C++ 11中有更好的方法吗? – 2013-07-24 14:47:44

+0

@MooingDuck可变参数函数不是可变参数模板函数。它们的性质和类型是不同的。 – rubenvb 2013-07-24 15:05:09

回答

63

的问题是,你不能使用“的printf”与va_args。如果您使用可变参数列表,则必须使用vprintf。 vprint,vsprintf中,vfprintf等(也有 '安全' 的版本在微软的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

您正在使用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++中使这种变体类型安全是不可能的,因为它必须通过类型擦除来工作。

7

我也不能确定你的纯

的意思在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); 
    ... 
} 
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); 
9

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

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

EDIT

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

2

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

Visual C++的示例。 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 
    } 
}