Question

J'ai ce morceau 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 passer par référence est préféré dans la mesure du 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 tout à fait sûr de ce qui se passe.

Était-ce utile?

La solution

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 format est un type valeur, il est placé sur la pile juste avant tous les arguments variadiques. Si format est un type de référence, seule l'adresse est placée dans 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, une AnsiString temporaire créée avant d'appeler Broken), pas l'adresse de l'argument.

Si vous ne souhaitez pas transmettre les classes complètes, vous avez le choix entre passer un pointeur ou insérer un argument factice:

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

Autres conseils

Voici ce que dit le standard C ++ (18.7 - Autre support d'exécution) sur va_start() (c'est moi qui souligne):

  

Les restrictions imposées par ISO C   le second paramètre à la   <stdarg.h> macro dans l'en-tête   parmN sont différents en cela   Standard international. Le paramètre   ... est l'identifiant du   paramètre le plus à droite dans la variable   liste de paramètres de la fonction   définition (celle juste avant la   <=>).    Si le paramètre <=> est déclaré avec une fonction, un tableau ou une référence   tapez ou avec un type qui n'est pas   compatible avec le type qui en résulte   en passant un argument pour lequel   il n'y a pas de paramètre, le comportement   n'est pas défini .

Comme d'autres l'ont déjà mentionné, l'utilisation de varargs en C ++ est dangereuse si vous l'utilisez avec des éléments non-straight-C (et éventuellement même d'une autre manière).

Ceci dit - j'utilise toujours printf () tout le temps ...

Une bonne analyse pour laquelle vous ne voulez pas que cela se trouve se trouve dans N0695

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

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

Ils ne sont pas sécurisés au niveau du type et ont un comportement UNDEFINED pour les objets de type classe, ce qui est probablement à l'origine du problème.

Voici ma solution de contournement facile (compilée 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);
}

Note latérale:

Le comportement des types de classe en tant qu'arguments varargs n'est peut-être pas défini, mais cela reste cohérent dans mon expérience. Le compilateur pousse sizeof (classe) de la mémoire de la classe sur la pile. C'est-à-dire en pseudo-code:

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

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

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top