As many here told: there is no standard API for adding data to va_list
, especially one-by-one. But there are some workarounds that may fit some cases.
Moreover va_list
has a different implementation on different platforms. That means that even if you reverse-engineer the implementation, there is no guarantee that this will work in future, because implementation may just be changed. And in order to make the code which uses that approach on other platforms - it will be necessary to repeat reverse-engineering.
The standard way to fill-in va_list
is to use a function with variadic number of args. There will be limitations - it won't be possible to add parameters one-by-one, but if we know that there is a maximum number of params that can be added, potentially it's possible to create if/else (generate the code) which will cover the possible range. Also, remember that this approach uses stack and C has a pretty limited stack anyway. Another challenge with this approach is - va_list works only in the scope of the function with variadic parameters and it's tricky to take va_list out. Here is a snippet (for educational purposes only, please don't use it for production. there are some flows, e.g. the code doesn't account for stackoverflow ;) ) which does the job (tested on Linux, Macos and Windows mingw):
/** @brief the cross-platform way to pass va_list back and forth in functions */
struct va_list_container {
va_list parameters;
};
// this must be big enough to cover stack for alloca
#define _va_list_padding (int)0x5555, (int)0x5555, (int)0x5555, (int)0x5555
static inline void va_list_container_start(void** pp, struct va_list_container * p, ...) {
*pp = (void*)&(pp); // store the top stack param addr
va_start(p->parameters, p);
}
/** @brief wrapper macro to put some things into va_list
example how to get va_list using all this combination of macroses
WITH_VA_LIST_CONTAINER(c,
VA_LIST_CONTAINER(c, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
vsnprintf(buf, sizeof(buf), "%d %d %d %d %d %d %d %d %d %d", c.parameters));
it outputs 1 2 3 4 5 6 7 8 9 10
WARNING: even though it works here - there is no guarantee that it will work everywhere and with everything Try not to use it. Reason:
A va_list is intrinsically linked to a specific function call's stack frame.
Creating a container to hold it doesn't change this fundamental characteristic.
Accessing it in a different function would involve undefined behavior due to potential stack frame changes.
*/
#define WITH_VA_LIST_CONTAINER(_name_, _in_...) do { \
void* pp = NULL; \
struct va_list_container _name_; \
_in_; \
va_end(_name_.parameters); \
}while(0)
/** @brief macro to get va_list from container and protect it with alloca
* alternativly we could use 2 alloca - first is to get the original stack addr
* and second is to cover pp value (which is the top of the used by va_list_container_start stack)
* this will be more precise
*/
#define VA_LIST_CONTAINER(_name_, _args_...) ({ \
va_list_container_start(&pp, &_name_, _args_, _va_list_padding); \
alloca(((char*)&pp)-((char*)pp)); \
&_name_; \
})
#define VA_LIST_FROM_CONTAINER(_name_, _args_...) VA_LIST_CONTAINER(_name_, _args_)->parameters
Now in order to make if/else, we could do something like (switch part can be generated as a macros):
WITH_VA_LIST_CONTAINER(c,
switch(num){
case 1: VA_LIST_CONTAINER(c, <val1>); break;
case 2: VA_LIST_CONTAINER(c, <val1>, <val2>); break;
...
}
// now we can use va_list stored in c.parameters, e.g.
// if (num == 10) {
// vsnprintf(buf, sizeof(buf), "%d %d %d %d %d %d %d %d %d %d", c.parameters));
// }
);
va_list
isn't actually a list as such, it's actually often a kind of a pointer to the stack, andva_arg
modifies this pointer to point to the next argument on the stack. – Progenitorva_start
? – Fissile