web-dev-qa-db-fra.com

Est-il sûr d'utiliser la macro va_start avec ceci comme paramètre?

Je dois utiliser le compilateur IAR dans une application intégrée (il n'a pas d'espaces de noms, d'exceptions, d'héritage multiple/virtuel, les modèles sont limités en bits et seul C++ 03 est pris en charge). Je ne peux pas utiliser le pack de paramètres, j'ai donc essayé de créer une fonction membre avec un paramètre variadic. Je sais que les paramètres variadiques sont généralement dangereux. Mais est-il sûr d'utiliser le pointeur this dans la macro va_start?

Si j'utilise une fonction variadique ordinaire, il faudrait un paramètre factice avant ... Pour pouvoir accéder aux paramètres restants. Je sais que la macro variadique n'aurait pas besoin de paramètre avant ... Mais je préférerais ne pas l'utiliser. Si j'utilise la fonction membre, il a masqué le paramètre this avant ... Alors je l'ai essayé .:

struct VariadicTestBase{
  virtual void DO(...)=0;
};

struct VariadicTest: public VariadicTestBase{
  virtual void DO(...){
    va_list args;
    va_start(args, this);
    vprintf("%d%d%d\n",args);
    va_end(args);
  }
};

//Now I can do
VariadicTestBase *tst = new VariadicTest;
tst->DO(1,2,3);

tst->DO(1,2,3); imprime 123 comme prévu. Mais je ne sais pas s'il ne s'agit pas simplement d'un comportement aléatoire/non défini. Je sais que tst->DO(1,2); planterait comme le ferait normalement un imprimeur. Cela ne me dérange pas.

18
user11373693

Rien ne spécifie ce comportement dans la norme, donc cette construction invoque simplement le comportement non défini formel. Cela signifie qu'il peut fonctionner correctement dans votre implémentation et entraîner une erreur de compilation ou des résultats inattendus dans une implémentation différente.

Le fait que les méthodes non statiques doivent passer le pointeur this caché ne peut garantir que va_start peut l'utiliser. Cela fonctionne probablement de cette façon car au début, les compilateurs C++ n'étaient que des pré-processeurs qui convertissaient la source C++ en source C et le paramètre caché this a été ajouté par le pré-processeur pour être disponible pour le compilateur C. Et il a probablement été maintenu pour compatibilité raisons. Mais je m'efforcerais d'éviter cela dans le code critique de la mission ...

20
Serge Ballesta

Semble être un comportement indéfini. Si vous regardez ce que va_start(ap, pN) fait dans de nombreuses implémentations (vérifiez votre fichier d'en-tête), il prend l'adresse de pN, incrémente le pointeur de la taille de pN et stocke le résultat dans ap. Pouvons-nous légalement regarder &this?

J'ai trouvé une référence Nice ici: https://stackoverflow.com/a/9115110/10316011

Citant la norme C++ 2003:

5.1 [expr.prim] Le mot-clé this nomme un pointeur vers l'objet pour lequel une fonction membre non statique (9.3.2) est invoquée. ... Le type de l'expression est un pointeur sur la classe de la fonction (9.3.2), ... L'expression est une valeur r.

5.3.1 [expr.unary.op] Le résultat de l'opérateur unaire & est un pointeur sur son opérande. L'opérande doit être une valeur l ou un qualifié_id.

Donc, même si cela fonctionne pour vous, ce n'est pas garanti et vous ne devez pas vous y fier.

3
user10316011

Je pense que cela devrait être OK, bien que je doute que vous trouverez une citation spécifique de la norme C++ qui le dit.

La justification est la suivante: va_start() doit être passé le dernier argument à la fonction. Une fonction membre ne prenant aucun paramètre explicite n'a qu'un seul paramètre (this), qui doit donc être son dernier paramètre.

Il sera facile d'ajouter un test unitaire pour vous alerter si jamais vous compilez sur une plateforme où cela ne fonctionne pas (ce qui semble peu probable, mais là encore vous compilez déjà sur une plateforme quelque peu atypique).

2
John Zwinck

Il s'agit d'un comportement indéfini. Étant donné que le langage n'exige pas que this soit transmis en tant que paramètre, il se peut qu'il ne le soit pas du tout.

Par exemple, si un compilateur peut comprendre qu'un objet est un singleton, il peut éviter de passer this comme paramètre et utiliser un symbole global lorsque l'adresse de this est explicitement requise (comme dans le cas de va_start). En théorie, le compilateur pourrait générer du code pour compenser cela dans va_start (après tout, le compilateur sait que c'est un singleton), mais il n'est pas obligé de le faire par la norme.

Pensez à quelque chose comme:

class single {
public:
   single(const single& )= delete;
   single &operator=(const single& )= delete;
   static single & get() {
       // this is the only place that can construct the object.
       // this address is know not later than load time:
       static single x;
       return x;
   }
  void print(...) {
      va_list args;
      va_start (args, this);
      vprintf ("%d\n", args);
      va_end (args);
}

private:
  single() = default;
};

Certains compilateurs, comme clang 8.0.0, émettent un avertissement pour le code ci-dessus:

prog.cc:15:23: warning: second argument to 'va_start' is not the last named parameter [-Wvarargs] va_start (args, this);

Malgré l'avertissement, ça marche bien . En général, cela ne prouve rien, mais c'est une mauvaise idée d'avoir un avertissement.

Remarque : Je ne connais aucun compilateur qui détecte les singletons et les traite spécialement, mais le langage n'interdit pas ce type d'optimisation. Si ce n'est pas fait aujourd'hui par votre compilateur, cela pourrait être fait demain par un autre compilateur.

Remarque 2: malgré tout cela, cela pourrait fonctionner dans la pratique pour le transmettre à va_start. Même si cela fonctionne, ce n'est pas une bonne idée de faire quelque chose qui n'est pas garanti par la norme.

Note 3 : La même optimisation singleton ne peut pas être appliquée à des paramètres tels que:

void foo(singleton * x, ...)

Il ne peut pas être optimisé car il peut avoir l'une des deux valeurs, pointer vers le singleton ou être nullptr. Cela signifie que cette préoccupation d'optimisation ne s'applique pas ici.

0
Michael Veksler