我希望在 C/C++ 中做到这一点。我遇到了可变长度参数,但这建议使用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
我错过了什么?
问题是你不能将 '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;
}
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
版本的函数。这个想法是使用汇编程序,不要触及堆栈上的参数,并暂时替换函数返回地址。
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 将支持可变参数模板,它提供了该问题的解决方案。
另一种解决方法可以通过巧妙的运算符重载来实现,以实现如下语法:
void f(varargs va) {
BOOST_FOREACH(varargs::iterator i, va)
cout << *i << " ";
}
f(args = 1, 2, 3, "Hello");
为了使其正常工作,必须实现类
varargs
来覆盖 operator =
,后者返回一个代理对象,而该代理对象又覆盖 operator ,
。然而,据我所知,在当前的 C++ 中使这种变体类型安全是不可能的,因为它必须通过类型擦除来工作。
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);
}
如果您只是想调用 printf, 有一个名为 vprintf 的 printf 变体,它需要 直接 va_list: vprintf(fmt, args);
我遇到了同样的问题,使用变量参数列表没有有可用的va_list
风格的函数,围绕SqLite3的
given函数创建包装器。我用
... → __VA_ARGS__
选择*宏'解决方案。除了获得更安全的代码之外,我总是为宏添加一个 pseudo 标头。 ({...})
是一个 gnu 编译器扩展,用于创建带有 return-value 的 macro。我的返回值是MK_ERROR
或MK_OK
/// \ingroup Sq3LiteC_Misc_C_API
enum MkErrorE Sq3LiteVtabConfig (SQ3_LITE sq3lite, Sq3VtabE op, ...);
#define Sq3LiteVtabConfig(sq3lite, op, ...) ({ \
Sq3ErrorE errVal = sqlite3_vtab_config(sq3lite->nat, op, __VA_ARGS__); \
Sq3ErrorE_Check_Macro(sq3lite,errVal); \
if (unlikely(Sq3ErrorCheckI(errVal))) { \
Sq3SqLite3ErrorToMQ(MkOBJ(sq3lite), __func__,__FILE__,__LINE__); \
MK_ERROR; \
} else { \
MK_OK; \
} \
})