如何包装可变长度参数的函数?
我期待在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擦除来工作。
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);