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

我期待在C / C ++中做到这一点。

我遇到了可变长度参数,但是这表明Python和C使用libffi解决scheme。

现在,如果我想用myprintf封装printf函数

我做的是如下所示:

 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 

我错过了哪些点?

问题是你不能用va_args使用'printf'。 如果使用可变参数列表,则必须使用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; } 

在C ++ 11中,这是使用Variadic templates一种可能的解决scheme:

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

编辑

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

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

在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); ... } 

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

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

你使用C还是C ++? 下一个C ++版本,C ++ 0x将支持可变模板 ,为这个问题提供解决scheme。

另一个解决方法是通过聪明的操作符重载来实现这样的语法:

 void f(varargs va) { BOOST_FOREACH(varargs::iterator i, va) cout << *i << " "; } f(args = 1, 2, 3, "Hello"); 

为了得到这个工作,类varargs必须被实现来覆盖operator =返回一个代理对象,这个代理对象又覆盖了operator , 。 然而,就我所知,在目前的C ++中使这个变体types安全是不可能的,因为它必须通过types擦除来工作。

你是指纯粹的C / C ++解决scheme?

其余参数(…)在C运行时支持跨平台。

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

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