tags:

views:

161

answers:

3

I hack some old C API and i got a compile error with the following code:

void OP_Exec( OP* op , ... )
{
    int i;
    va_list vl;
    va_start(vl,op);
    for( i = 0; i < op->param_count; ++i )
    {
        switch( op->param_type[i] )
        {
            case OP_PCHAR:
                op->param_buffer[i] = va_arg(vl,char*); // ok it works
            break;
            case OP_INT:
                op->param_buffer[i] = &va_arg(vl,int); // error here
            break;
            // ... more here
        }
    }
    op->pexec(op);
    va_end(vl);
}

The error with gcc version 4.4.1 (Ubuntu 4.4.1-4ubuntu9) was:

 main.c|55|error: lvalue required as unary ‘&’ operand

So why exactly it's not possible here to get a pointer to argument?

How to fix it? This code is executed very often with different OP*, so i prefer to not allocate extra memory.

Is it possible to iterate over va_list knowing only the size of arguments?

+2  A: 

Change param_buffer to be an array of

struct ValueUnion {
  Type type;
  union {
    char *stringval;
    int intval;
  } u;
};

Then you can say

op->param_buffer[i].type = op->param_type[i];
switch( op->param_type[i] )
{
    case OP_PCHAR:
        op->param_buffer[i].u.stringval = va_arg(vl,char*); // ok it works
    break;
    case OP_INT:
        op->param_buffer[i].u.intval = va_arg(vl,int); // ok it works
    break;
    // ... more here
}

You can't get the address of a variadic arg.

Johannes Schaub - litb
OK thx, it probably will work, but i load op->pexec from dynamic library and i cannot rewrite the other modules right now.
lionbest
+1  A: 

Since litb's answer isn't useable for you because you can't modify the pexec() function, you could fix this using alloca() if your compiler provides it:

    switch( op->param_type[i] )
    {
        int *itmp;

        case OP_PCHAR:
            op->param_buffer[i] = va_arg(vl,char*); // ok it works
        break;

        case OP_INT:
            itmp = alloca(sizeof(int));
            *itmp = va_arg(vl, int);
            op->param_buffer[i] = itmp;
        break;
        // ... more here
    }

alloca() is usually blindingly fast, since it often is implemented using the same mechanism that is used to allocate space for local variables. The space will be automatically deallocated when the calling function exits.

caf
Thx, it work great. I check the disassembled code and it seams perfect to me. I used more compact code `*((int*)(op->param_buffer[i] = alloca( sizeof(int) ))) = va_arg(vl,int);` .
lionbest
It would be easier to follow for future maintainers if you broke that up into two lines: `op->param_buffer[i] = alloca(sizeof(int)); *(int *)op->param_buffer[i] = va_arg(vl, int);`
caf
A: 

It won't be portable, but on some implementations, va_list is a char * to the address of the parameter on the stack. Since some platforms pass arguments in registers, and due to stack alignment issues, you don't really want to do the following:

If this is for a single platform, you could look at its stdarg.h and hack up a solution to get to the address of the parameter on the stack.

Major hack though, and not really a good idea.

tomlogic
I believe that all variable length arguments are always passed on the stack, not in registers.
nategoose