How do I fill a va_list
Asked Answered
F

6

21

If I have a va_list I know how to extract all its elements:

void printInts(int n,...)
{
    va_list va;
    va_start(va, n);
    for(unsigned int i=0; i<n; i++)
    {
        int arg=va_arg(va, int);
        printf("%d",arg);
    }
    va_end(va);
} 

So when I call printInts(3,1,2,3) the va_list get filled of all the parameters.
But how do I manually fill a va_list without using va_start? I mean that I want something like:

va_list va;
push_arg(va, int, 5); // And so on until I fill all parameters
...

I need this because there is a function that accept a va_list as argument, and I don't know how to fill that va_list of all its parameters.

Fife answered 4/12, 2012 at 13:11 Comment(4)
The type va_list isn't actually a list as such, it's actually often a kind of a pointer to the stack, and va_arg modifies this pointer to point to the next argument on the stack.Progenitor
@JoachimPileborg: Sometimes it's not a pointer to the stack.Michellemichels
Why don't you want to use va_start?Fissile
Because I need a raw va_list with all it's argument, I need to do the opposite thing than va_start-va_arg-va_end .Fife
B
23

There's no ability to fill a va_list explicitly.

You should write a wrapper function. Say you need to call your function foo, instead of manually filling in a va_list, you define a new function like so:

void call_foo(int arg1, ...)
{
   va_list ap;
   va_start(ap, arg1);
   foo(arg1, ap);
   va_end(ap);
}

Now you can call foo, which takes a va_list, however you like, by doing e.g. call_foo(1,2,3,4);, call_foo(1, 1, "Hello"); etc.

This will only allow you to specify the arguments at compile time, you can't build the arguments at runtime.

Bagby answered 4/12, 2012 at 13:19 Comment(0)
O
14

Normally, these functions come in pairs. If you have a "va-accepting" function, it is easy to create another one:

void printInts_v(int n, va_list ap)
{
    unsigned int i=0;
    for(i=0; i<n; i++)
    {
        int arg=va_arg(ap, int);
        printf("%d", arg);
    }
}

This function can be called this way:

void printInts(int n,...)
{
    va_list ap;
    va_start(ap, n);
    printInts_v(n, ap);
    va_end(va);
}

But I don't think there is a way to portably pre-fill a va_list for later use.

If you work on one architecture and portability is not an issue, you could craft something on your own, however. How exactly to do that is platform-specific.

Okinawa answered 4/12, 2012 at 13:16 Comment(0)
M
0

I don't think there's a standardized way of doing this. On the other hand, such a pre-filled va_list would be pretty useless, since there's no way of "querying" it for the number and types of available arguments, either.

Mom answered 4/12, 2012 at 13:14 Comment(6)
I think the question is like "how do I call vprintf()?" and the answer is "write printf() and call it from there".Michellemichels
There is a function of a framework that accepts a va_list, how do I go around this?Fife
@RamyAlZuhouri As written in - up to now - two answers.Okinawa
@glglgl: (I know this is quite an old question but) none of those answers are usable. because they all assume you know the argument list at compile time. The real problem, is about building the va_list dynamically.Gamb
@Gamb That cannot be done, at least not in portable standard C, which is what the answers are saying.Mom
@unwind, what the answers say is that there is no portable way to build a va_list. Ramy AlZuhouri's question glglgl was replying was about the possibility of a work around solution.Gamb
G
0

Since other replies tells it's impossible (which would be a good reason to add it in the design of the language for the next version), The only portable possibility would be

  • to check the documentation twice to investigate if you didn't miss or misunderstand something about the framework you're trying to use : I had the exact same problem here and it appeared in the answer that I was not using the right call for what I was trying to do;

  • to write yourself a function which would take an array of pointers as parameter instead of the va_list and split the problem in multiple calls (not always possible); or

  • assume the framework you're trying to use was poorly designed...

Gamb answered 7/5, 2019 at 11:55 Comment(0)
L
0

Splitting the function into multiple calls works for the printf family - there is code for this in Kernighan & Ritchie, along the lines of:

void printf_OclFile(struct OclFile* self, char* f, void* sq[])
{ int n = length(sq);
  if (n == 0 || self->actualFile == NULL) 
  { return; }

  void* ap; 
  char* p = f; 
  int i = 0; 

  char* sval; 
  int ival; 
  double dval; 

  ap = sq[0]; 
  for ( ; *p != '\0' && i < n; p++)
  { if (*p != '%')
    { char* p0 = subString(p,1,1); 
      fputs(p0, self->actualFile); 
      continue; 
    } 

    p++; /* Points to flag after % */ 

    switch (*p)
    { case 'd' : 
        ival = *((int*) sq[i]); 
        i++; 
        fprintf(self->actualFile, "%d", ival);
        break; 
      case 'f' : 
        dval = *((double*) sq[i]); 
        i++; 
        fprintf(self->actualFile, "%f", dval); 
        break; 
      case 's' : 
        sval = ((char*) sq[i]); 
        i++; 
        fprintf(self->actualFile, "%s", sval); 
        break;
      default : 
        char* p0 = subString(p,1,1); 
        fputs(p0, self->actualFile);
        break; 
    }
  } 
}
Lir answered 24/10, 2021 at 18:13 Comment(0)
M
0

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));
        // }
    );
Microwave answered 26/7 at 2:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.