2010-04-19 3 views
1

Je pirater une ancienne API C et je suis une erreur de compilation avec le code suivant:Comment obtenir l'adresse de va_arg?

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); 
} 

L'erreur avec gcc version 4.4.1 (Ubuntu 4.4.1-4ubuntu9) était:

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

Alors pourquoi exactement il est impossible ici d'obtenir un pointeur vers l'argument?

Comment le réparer? Ce code est exécuté très souvent avec OP* différent, donc je préfère ne pas allouer de la mémoire supplémentaire.

Est-il possible d'itérer sur va_list en ne connaissant que la taille des arguments?

+0

va_arg est une macro, vous ne pouvez pas prendre d'adresse de celui-ci. –

Répondre

1

Depuis litb's answer n'est pas utilisable pour vous parce que vous ne pouvez pas modifier la fonction pexec(), vous pouvez résoudre ce en utilisant alloca() si votre compilateur le fournit:

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() est généralement rapide aveuglante, car souvent est implémenté en utilisant le même mécanisme que celui utilisé pour allouer de l'espace aux variables locales. L'espace sera automatiquement libéré lorsque la fonction d'appel sera terminée.

+0

Thx, ça marche super bien. Je vérifie le code démonté et ça me semble parfait. J'ai utilisé un code plus compact '* ((int *) (op-> param_buffer [i] = alloca (taille de (int)))) = va_arg (vl, int);'. – Arpegius

+1

Il serait plus facile de suivre pour les futurs responsables si vous avez divisé cela en deux lignes: 'op-> param_buffer [i] = alloca (sizeof (int)); * (int *) op-> param_buffer [i] = va_arg (vl, int); ' – caf

2

changement param_buffer être un tableau de

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

Ensuite, vous pouvez dire

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 
} 

Vous ne pouvez pas obtenir l'adresse d'un arg variadique.

+0

OK thx, ça marchera probablement, mais je charge op-> pexec de la librairie dynamique et je ne peux pas réécrire les autres modules maintenant. – Arpegius

0

Il ne sera pas portable, mais sur certaines implémentations, va_list est un char * à l'adresse du paramètre sur la pile. Étant donné que certaines plates-formes transmettent des arguments dans des registres et en raison de problèmes d'alignement, vous ne voulez pas vraiment faire ce qui suit:

S'il s'agit d'une plate-forme unique, vous pouvez consulter son fichier stdarg.h et hacher un solution pour arriver à l'adresse du paramètre sur la pile.

Majeur hack cependant, et pas vraiment une bonne idée.

+0

Je crois que tous les arguments de longueur variable sont toujours passés sur la pile, pas dans les registres. – nategoose