Existem armadilhas usando VarArgs com os parâmetros de referência
-
03-07-2019 - |
Pergunta
Eu tenho este pedaço de código (resumido) ...
AnsiString working(AnsiString format,...)
{
va_list argptr;
AnsiString buff;
va_start(argptr, format);
buff.vprintf(format.c_str(), argptr);
va_end(argptr);
return buff;
}
E, na base de que passagem por referência é o preferido sempre que possível, o texto foi alterado desta forma.
AnsiString broken(const AnsiString &format,...)
{
... the rest, totally identical ...
}
Meu código de chamada é assim: -
AnsiString s1, s2;
s1 = working("Hello %s", "World");
s2 = broken("Hello %s", "World");
Mas, S1 contém "Olá Mundo", enquanto s2 tem "Olá (null)". Eu acho que isso é devido às obras va_start maneira, mas eu não sei exatamente o que está acontecendo.
Solução
Se você olhar para o que va_start expande para fora, você vai ver o que está acontecendo:
va_start(argptr, format);
torna-se (aproximadamente)
argptr = (va_list) (&format+1);
Se format é um tipo de valor, ele é colocado na pilha direita antes de todos os argumentos variádicos. Se format é um tipo de referência, apenas o endereço é colocado na pilha. Quando você toma o endereço da variável de referência, você obter o endereço ou a variável original (neste caso de um AnsiString temporário criado antes de chamar quebrado), não o endereço do argumento.
Se você não quer passar em torno de classes completas, suas opções são: ou passagem pelo ponteiro, ou colocar em um argumento fictício:
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");
Outras dicas
Aqui está o que o C ++ padrão (18,7 - Outro suporte de tempo de execução) diz sobre va_start()
(grifo meu):
As restrições que a ISO C coloca em o segundo parâmetro para o macro
va_start()
no cabeçalho<stdarg.h>
são diferentes neste Padrão internacional. o parâmetroparmN
é o identificador do parâmetro mais à direita na variável lista de parâmetros da função definição (o que pouco antes da...
). Se oparmN
parâmetro é declarado com uma função, uma matriz ou referência tipo, ou com um tipo que não é compatível com o tipo de que os resultados quando passar um argumento para que não há nenhum parâmetro, o comportamento é indefinido .
Como já foi mencionado, usando varargs em C ++ é perigoso se você usá-lo com itens não-linear, C (e possivelmente até mesmo de outras maneiras).
Dito isto - eu ainda usar printf () o tempo todo ...
Uma análise bom porque você não quer que isso é encontrado em N0695
De acordo com as Normas de Codificação C ++ (Sutter, Alexandrescu):
VarArgs nunca deve ser usado com C ++:
Eles não são do tipo seguro e ter um comportamento indefinido para objetos do tipo classe, que é provavelmente causando o problema.
Aqui está a minha solução fácil (compilado com 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);
}
Nota lateral:
O comportamento para tipos de classe como varargs argumentos podem ser indefinido, mas é consistente na minha experiência. O compilador empurra sizeof (classe) da memória da classe na pilha. Ou seja, em pseudo-código:
alloca(sizeof(class));
memcpy(stack, &instance, sizeof(class);
Para um exemplo muito interessante deste sendo utilizados de uma forma muito criativa, a observação que você pode passar uma instância de CString no lugar de um LPCTSTR para um varargs função diretamente, e ele funciona, e há não lançando os envolvidos. Eu deixá-lo como um exercício para o leitor a descobrir como eles fizeram esse trabalho.