How to wrap a function with variable length arguments?
我正在寻找在C / C ++中执行此操作。
我遇到了可变长度参数,但这建议使用libffi使用Python&C解决方案。
现在,如果我想用myprintf包装printf函数
我所做的如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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 \
this is a character: %c and \
another number: %d\
",a, v, b);
return 0;
} |
但是结果却不如预期!
1 2 3
| This is a number: 1244780 and
this is a character: h and
another number: 29953463 |
我错过了什么地方?
问题是您不能将'printf'与va_args一起使用。如果使用变量参数列表,则必须使用vprintf。 vprint,vsprintf,vfprintf等(Microsoft C运行时中也有"安全"版本,可防止缓冲区溢出等)
您的示例工作如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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 \
this is a character: %c and \
another number: %d\
",a, v, b);
return 0;
} |
在C ++ 11中,这是使用Variadic templates的一种可能的解决方案:
1 2 3 4 5
| template<typename... Args>
void myprintf(const char* fmt, Args... args )
{
std::printf( fmt, args... ) ;
} |
编辑
正如@rubenvb指出的,需要权衡考虑,例如,您将为每个实例生成代码,这将导致代码膨胀。
我也不确定你是什么意思
在C ++中,我们使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #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版本的函数。这个想法是使用汇编程序,不要触摸堆栈中的参数,而是临时替换函数的返回地址。
Visual C x86的示例。 call addr_printf调用printf():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| __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\")\
", fmt);
}
static void __stdcall _dbg_printf_end(int ret) {
printf("printf() returned %d\
", 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将支持可变参数模板,这些模板提供了该问题的解决方案。
通过巧妙的运算符重载来实现如下语法,可以实现另一种解决方法:
1 2 3 4 5 6
| void f(varargs va) {
BOOST_FOREACH(varargs::iterator i, va)
cout << *i <<"";
}
f(args = 1, 2, 3,"Hello"); |
为了使它起作用,必须实现类varargs来覆盖operator =,该operator =返回一个代理对象,该代理对象又覆盖operator ,。但是,据我所知,在当前的C ++中使这种变型类型安全是不可能的,因为它必须通过类型擦除来工作。
1 2 3 4 5 6 7 8 9 10
| 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); |
您是什么意思纯C / C ++解决方案?
C运行时跨平台支持rest参数(...)。
http://msdn.microsoft.com/zh-CN/library/kb57fad8.aspx
|