not_null - github c++ guidelines



Y a-t-il des gotchas utilisant des varargs avec des paramètres de référence (4)

Note latérale:

Le comportement des types de classe en tant qu'arguments varargs peut être indéfini, mais il est cohérent dans mon expérience. Le compilateur pousse sizeof (class) de la mémoire de la classe sur la pile. Autrement dit, en pseudo-code:

alloca(sizeof(class));
memcpy(stack, &instance, sizeof(class);

Pour un exemple très intéressant d'utilisation très créative, notez que vous pouvez passer une instance CString à la place d'un LPCTSTR directement à une fonction varargs, et cela fonctionne, et il n'y a pas de conversion. Je laisse au lecteur le soin de comprendre comment ils ont fait ce travail.

J'ai ce bout de code (résumé) ...

AnsiString working(AnsiString format,...)
{
    va_list argptr;
    AnsiString buff;

    va_start(argptr, format);
    buff.vprintf(format.c_str(), argptr);

    va_end(argptr);
    return buff;
}

Et, sur la base que le passage par référence est préféré lorsque cela est possible, je l'ai changé ainsi.

AnsiString broken(const AnsiString &format,...)
{
... the rest, totally identical ...
}

Mon code d'appel est comme ceci: -

AnsiString s1, s2;
    s1 = working("Hello %s", "World");
    s2 = broken("Hello %s", "World");

Mais, s1 contient "Hello World", alors que s2 a "Hello (null)". Je pense que cela est dû à la façon dont va_start fonctionne, mais je ne suis pas vraiment sûr de ce qui se passe.


Answer #1

Selon les normes de codage C ++ (Sutter, Alexandrescu):

varargs ne devrait jamais être utilisé avec C ++:

Ils ne sont pas de type sécurisé et ont un comportement UNDEFINED pour les objets de type classe, ce qui est susceptible de causer votre problème.


Answer #2

Voici ma solution de contournement facile (compilé avec Visual C ++ 2010):

void not_broken(const string& format,...)
{
  va_list argptr;
  _asm {
    lea eax, [format];
    add eax, 4;
    mov [argptr], eax;
  }

  vprintf(format.c_str(), argptr);
}

Answer #3

Si vous regardez ce que va_start développe, vous verrez ce qui se passe:

va_start(argptr, format); 

devient (à peu près)

argptr = (va_list) (&format+1);

Si le format est un type valeur, il est placé sur la pile juste avant tous les arguments variadiques. Si le format est un type de référence, seule l'adresse est placée sur la pile. Lorsque vous prenez l'adresse de la variable de référence, vous obtenez l'adresse ou la variable d'origine (dans ce cas, un AnsiString temporaire créé avant d'appeler Broken), et non l'adresse de l'argument.

Si vous ne voulez pas passer de classe complète, vos options sont soit de passer par un pointeur, soit de mettre un argument fictif:

AnsiString working_ptr(const AnsiString *format,...)
{
    ASSERT(format != NULL);
    va_list argptr;
    AnsiString buff;

    va_start(argptr, format);
    buff.vprintf(format->c_str(), argptr);

    va_end(argptr);
    return buff;
}

...

AnsiString format = "Hello %s";
s1 = working_ptr(&format, "World");

ou

AnsiString working_dummy(const AnsiString &format, int dummy, ...)
{
    va_list argptr;
    AnsiString buff;

    va_start(argptr, dummy);
    buff.vprintf(format.c_str(), argptr);

    va_end(argptr);
    return buff;
}

...

s1 = working_dummy("Hello %s", 0, "World");




variadic-functions