How to wrap a function with variable length arguments?
Solution 1
The problem is that you cannot use 'printf' with va_args. You must use vprintf if you are using variable argument lists. vprint, vsprintf, vfprintf, etc. (there are also 'safe' versions in Microsoft's C runtime that will prevent buffer overruns, etc.)
You sample works as follows:
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;
}
Solution 2
In C++11, this is one possible solution using variadic templates:
template<typename... Args>
void myprintf(const char* fmt, Args... args)
{
std::printf(fmt, args...);
}
As rubenvb points out, there are trade-offs to consider. For example, you will be generating code for each instance which will lead to code bloat.
Solution 3
I am also unsure what you mean by pure.
In C++ we use:
#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);
...
}
Solution 4
Actually, there's a way to call a function that doesn’t have a va_list
version from a wrapper. The idea is to use assembler, do not touch arguments on the stack, and temporarily replace the function return address.
An example for Visual C x86. call addr_printf
calls 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
}
}
Solution 5
Are you using C or C++? The next C++ version, C++0x, will support variadic templates which provide a solution to that problem.
Another workaround can be achieved by clever operator overloading to achieve a syntax like this:
void f(varargs va) {
BOOST_FOREACH(varargs::iterator i, va)
cout << *i << " ";
}
f(args = 1, 2, 3, "Hello");
In order to get this to work, the class varargs
has to be implemented to override operator =
that returns a proxy object which, in turn, overrides operator ,
. However, making this variant type safe in current C++ isn't possible as far as I know since it would have to work by type erasure.
Comments
-
prakash almost 2 years
I am looking to do this in C/C++.
I came across Variable Length Arguments but this suggests a solution with Python & C using libffi.
Now, if I want to wrap
printf
function withmyprintf
What I do is like below:
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; }
But the results are not as expected!
This is a number: 1244780 and this is a character: h and another number: 29953463
Any point where did I miss??
-
Mooing Duck about 11 yearsC++03 could use
boost::tuple
which has ways to do the above safely. -
Francesco Dondi almost 8 yearsI wouldn't even dare ever to write this, but I cannot not admire it.
-
John Strood over 7 yearsCan you shed some light on why "you cannot use
printf
withva_args
" ? Whyvprintf
? -
erco over 6 years@JohnStrood To answer this directly: because printf() does not accept a 'va_list' as an argument, it expects a variable number of arguments (e.g. "...") which is different. See the man page for printf() and vprintf(). No where does it say printf() accepts 'va_list' as an argument, only a variable number of arguments of the type the % format codes expect (int, long, float, etc). Only the vprintf() family of functions accept a va_list.
-
Chris Reid almost 6 yearsAlso be warned the argument format checking of printf and scanf family of functions does not work with templates. The format string is not checked. If you get the format string wrong It will not be caught at compile-time, but may segfault (crash) or have unknown behavior at run-time.
-
Chris Reid almost 6 yearsYou can add a compiler attribute to the function def and have the compiler check your format arguments. class Foo { attribute ((format (printf, 2, 3))) void Write(const char* pMsg, ...); }; Foo f; f.Write( "%s %s %d %s" , "dog" , "cat", "horse", "pig"); "warning: format specifies type 'int' but the argument has type 'const char *' [-Wformat]"
-
user7082181 almost 3 years1 you gotta use const char 2 you definetely cannot use fmt as a string buffer for vprintf...did you test this code ???
-
Andak over 2 yearsThanks! I tested this with the nios2-elf-gcc compiler and it works like a charm!