Click here to Skip to main content
15,892,575 members
Articles / Programming Languages / C++
Article

va_list, va_start, va_pass!!! or how to pass variable-argument list to next va-function

Rate me:
Please Sign up or sign in to vote.
2.58/5 (20 votes)
29 Mar 2005CPOL 115.6K   15   13
trick to pass variable-argument list to next va-function

Introduction

I've been looking for solution of passing variable-argument list from my va-function to another one, like TRACE for example. All solutions I saw were about using special functions that take va_list as argument. But this is a un-straight way. Why couldn't I just pass "..." to next function? C++ syntax doesn't allow this. But C++ allows to extend itself. Let me introduce you new macros from va_ set:

template<byte count>
struct SVaPassNext{
    SVaPassNext<count-1> big;
    DWORD dw;
};
template<> struct SVaPassNext<0>{};
//SVaPassNext - is generator of structure of any size at compile time.

class CVaPassNext{
public:
    SVaPassNext<50> svapassnext;
    CVaPassNext(va_list & args){
		try{//to avoid access violation
			memcpy(&svapassnext, args, sizeof(svapassnext));
		} catch (...) {}
    }
};
#define va_pass(valist) CVaPassNext(valist).svapassnext

#if 0 //using:
void MyVA_Function(szFormat, ...){
    va_list args;
    va_start(args, szFormat);
    SomeOtherVA_Function(szFormat, va_pass(args));
    va_end(args);
}
#endif
how this works:
I just copy 50 * sizeof(DWORD) bytes of stack to my struct of this size and simply pass this struct as ... argument to next function. Compiler just copies my copy of local stack to next function stack. And that's all we need.

Enjoy!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Russian Federation Russian Federation
As programmer I understand that every program takes brain to be created. The more complex is the software, the more senior developers it takes. And they say that DNA doesn't have an author? I don't buy it.

Comments and Discussions

 
QuestionGood! Pin
WuRunZhe5-Dec-13 20:58
WuRunZhe5-Dec-13 20:58 
SuggestionPortable or not, variadic functions themselves are evil Pin
Pa3PyX14-Dec-11 12:25
Pa3PyX14-Dec-11 12:25 
Here is a more straightforward implementation of your va_pass using only the standard compiler means, and it also does away with C++:
C++
#include <stdarg.h>

/* Replace with your best guesstimate of the max total unnamed argument size */
#define VA_MAX_SIZE 256

struct _va_block_s {
    char _dummy[VA_MAX_SIZE];
};

#define va_pass(args) va_arg(args, struct _va_block_s)


/* Example usage */
#include <stdio.h>

int my_printf(const char *format, ...) {
    int ret_val;
    va_list args;

    va_start(args, format);
    /* Stuff approximately 60 more arguments than we actually need... */
    ret_val = printf(format, va_pass(args));
    va_end(args);
    return ret_val;
}

Like the original, va_pass just fetches a 256-byte block of memory from the parameter stack of the current variadic function (here, my_printf()); it is then pushed onto the stack for the variadic function we are calling (in this case, printf()). It's still not quite portable, since (1) it assumes the compiler is using the stack in a similar manner as in the x86 architecture, and (2) it assumes the calling convention of the parent call to be the same as that of the child. If the compiler uses some exotic calling convention that is not remotely similar to __cdecl---it may break.

With MSVC (and probably other x86 compilers), we do not have to check for possible access violations as we haul stuff on the stack because of the way Windows dynamically grows the stack. It first *reserves* a certain amount of space (normally 1 MB) for the stack and *commits* a lesser amount, normally one page (4 KB) at the top of the stack (recall that the stack grows downwards on x86). That is, there is 1 MB of contiguous address space available for use by the stack, but only a few kilobytes are initially committed (allocated); at the bottom of the allocated memory, there is a *guard page*. As things get pushed on the stack and it grows, eventually the guard page is accessed, which raises an exception and causes the program to allocate another page for the stack---and so on up to the number of reserved pages. Only if you ever run out of reserved pages (for example, due to an infinite recursion) will the program crash with a stack overflow. If that occurs after you have just written under a page of data, then you have a bigger problem than just lack of portability---your stack is almost full, and you need to relink the program with a switch to reserve more memory for the stack. By the way, if the total size of the local variables used by the function exceeds one page, MSVC will insert a *stack probe* in the beginning of your function to make sure there is enough stack space committed before any local variables are accessed (as there is no guarantee that they'll be accessed in order). If you ever disassembled such functions, you might have seen an interesting call named _alloca_probe(); that's what it's for.

So it is relatively safe to read or write the stack below the current ESP value (i.e. beyond the edge of valid data), as long as it is done incrementally---in no more than page-sized (4 KB) increments. On the other hand, catching access violations while accessing the stack is strongly discouraged, because doing so may prevent the default exception handler from allocating more memory for the stack!

But even if we forsake portability and consider just x86 architecture, this approach too just works around the problem without addressing the root cause, much as the original va_pass. The root cause lies in the way variadic functions themselves are implemented by major C/C++ compilers. If you browse through shdarg.h header, you'll notice that among va_list, va_arg, va_start and other va_crap, one thing that is blatantly missing (besides your va_pass) is va_size. The problem is that there is no reliable (let alone portable) way, from within variadic function, to tell the total size of the unnamed arguments it received on the stack---and thus, no good way to copy these arguments for the next variadic function to use. The calling conventions used provide no way to infer the size of the argument list received---nothing short of examining machine code at the return address, or perhaps analyzing stack frames (whose use by the compiler is optional to begin with). Only the *caller* of the variadic function knows the size, because it knows exactly how many arguments it pushed:
C++
my_printf("%lf %c %ld", 4.7, 'A', -17);

ASM
push    -17             ; Pass an int (pointer-sided argument) (4 bytes)
push    65              ; Pass a char (aligned to pointer-sized, still 4 bytes)
sub     esp, 8          ; Reserve room for a double-sized argument (8 bytes)
fstp    qword ptr[esp]  ; Pass a double
push    offset format   ; Pass the format string pointer (4 bytes)
call    my_printf       ; Nothing on the parameter stack indicates total vararg size or types;
...                     ;   it's just a block of memory with known base but unknown size
add     esp, 20         ; *Somewhere* after the call, clean up the stack, rewinding it by the total
                        ;   size of passed arguments. This number changes based on the number of
                        ;   arguments actually passed, but the instruction is not guaranteed to
                        ;   immediately follow the call, or even be this exact instruction...

I don't know why C/C++ compilers use such a horrible implementation for variadic functions; more than anything, the reasons are probably historical---people have DLLs, people have libs, and they expect these to work with future versions of the compilers. Imagine MSVC changing the calling convention for variadic calls, and then all your fprintf()'s break (not like that ever stopped Microsoft, mind you).

But regardless, I guess the bottom line is that the only reliable way to wrap a variadic call into another variadic call in C is to use variadic macros instead of variadic functions:
C++
#define my_printf(...) { \
	/* Declare local variables, do something */ \
	printf(__VA_ARGS__); \
	/* Do something else */ \
}

/* Or */

#define my_printf(...) ( \
	/* Evaluate some expressions separated with a comma */
	printf(__VA_ARGS__), \
	/* Evaluate the return result */
)

Of course, as with all macros, you are limited to either returning no value (in the first case) or declaring no variables (in the second case), unless you use e.g. proprietary GCC extensions which allow you to have void-valued expressions in a comma-separated list; or if you use C++, you may get inventive with templates.
GeneralMy vote of 2 Pin
ZoogieZork6-Apr-09 18:41
ZoogieZork6-Apr-09 18:41 
General[Message Deleted] Pin
Igor Mihailov19-Feb-08 2:04
Igor Mihailov19-Feb-08 2:04 
GeneralRe: Crash on memcpy Pin
araud19-Feb-08 2:14
araud19-Feb-08 2:14 
Generalnot portable Pin
peterchen29-Mar-05 6:20
peterchen29-Mar-05 6:20 
GeneralRe: not portable Pin
araud29-Mar-05 18:55
araud29-Mar-05 18:55 
GeneralRe: not portable Pin
peterchen29-Mar-05 19:18
peterchen29-Mar-05 19:18 
Generalportable Pin
araud29-Mar-05 21:41
araud29-Mar-05 21:41 
GeneralRe: portable Pin
Anonymous22-Jun-05 2:19
Anonymous22-Jun-05 2:19 
GeneralRe: portable Pin
Jan Richardson7-Aug-05 1:48
Jan Richardson7-Aug-05 1:48 
GeneralRe: portable Pin
__PPS__18-Apr-07 14:37
__PPS__18-Apr-07 14:37 
GeneralRe: portable Pin
araud18-Apr-07 18:52
araud18-Apr-07 18:52 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.