Passando número variável de argumentos ao redor

333

Digamos que eu tenho uma função C que recebe um número variável de argumentos: Como posso chamar outra função que espera um número variável de argumentos dentro dela, passando todos os argumentos que entraram na primeira função?

Exemplo:

void format_string(char *fmt, ...);

void debug_print(int dbg_lvl, char *fmt, ...) {
    format_string(fmt, /* how do I pass all the arguments from '...'? */);
    fprintf(stdout, fmt);
 }
Vicent Marti
fonte
4
Seu exemplo parece um pouco estranho para mim, na medida em que você passa fmt para format_string () e para fprintf (). Format_string () deve retornar uma nova string de alguma forma?
Kristopher Johnson
2
Exemplo não faz sentido. Foi apenas para mostrar o esboço do código.
Vicent Marti
162
"deve ser pesquisado no Google": eu discordo. O Google tem muito ruído (informações pouco claras e muitas vezes confusas). Ter uma boa resposta votada e aceita no stackoverflow realmente ajuda!
Ansgar
71
Apenas para ponderar: cheguei a essa pergunta do google e, como era um estouro de pilha, estava altamente confiante de que a resposta seria útil. Então pergunte!
24410 tenpn
32
@Ilya: se ninguém escrevesse coisas fora do Google, não haveria informações a serem pesquisadas no Google.
Erik Kaplun

Respostas:

211

Para passar as elipses, você deve convertê-las em uma va_list e usar essa va_list em sua segunda função. Especificamente;

void format_string(char *fmt,va_list argptr, char *formatted_string);


void debug_print(int dbg_lvl, char *fmt, ...) 
{    
 char formatted_string[MAX_FMT_SIZE];

 va_list argptr;
 va_start(argptr,fmt);
 format_string(fmt, argptr, formatted_string);
 va_end(argptr);
 fprintf(stdout, "%s",formatted_string);
}
SmacL
fonte
3
O código é retirado da pergunta e é realmente apenas uma ilustração de como converter elipses em vez de algo funcional. Se você observar, format_stringtambém dificilmente será útil, pois teria que fazer modificações in situ no fmt, o que certamente também não deveria ser feito. As opções incluiriam livrar-se de format_string completamente e usar vfprintf, mas isso faz suposições sobre o que format_string realmente faz, ou faz com que format_string retorne uma string diferente. Vou editar a resposta para mostrar a última.
SmacL
11
Se a sua string de formato usar os mesmos comandos da string de impressão, você também poderá obter alguns compiladores como gcc e clang para avisar se a sua string de formato não for compatível com os argumentos reais transmitidos. Consulte o atributo da função GCC ' formato 'para obter mais detalhes: gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html .
Doug Richardson
11
Isso não parece funcionar se você estiver passando os argumentos duas vezes seguidas.
Fotanus
2
@fotanus: se você chamar uma função com a função argptre chamada argptr, a única coisa segura a fazer é chamar va_end()e reiniciar va_start(argptr, fmt);para reinicializar. Ou você pode usar va_copy()se o seu sistema suportar (C99 e C11 exigem; C89 / 90 não).
Jonathan Leffler
11
Observe que o comentário de @ ThomasPadron-McCarthy está desatualizado e a impressão final final está ok.
Frederick
59

Não há como chamar (por exemplo) printf sem saber quantos argumentos você está passando, a menos que queira fazer truques impertinentes e não portáteis.

A solução geralmente usada é sempre fornecer uma forma alternativa de funções vararg, assim printfcomo a vprintfque va_listsubstitui a .... As ...versões são apenas wrappers em volta das va_listversões.


fonte
Observe que nãovsyslog é compatível com POSIX .
Patryk.beza
53

Funções variáveis podem ser perigosas . Aqui está um truque mais seguro:

   void func(type* values) {
        while(*values) {
            x = *values++;
            /* do whatever with x */
        }
    }

func((type[]){val1,val2,val3,val4,0});
Rose Perrone
fonte
11
Ainda melhor é esse truque: #define callVardicMethodSafely(values...) ({ values *v = { values }; _actualFunction(values, sizeof(v) / sizeof(*v)); })
Richard J. Ross III
5
@ RichardJ.RossIII Eu gostaria que você expandisse seu comentário, é dificilmente legível assim, não consigo entender a idéia por trás do código e, na verdade, parece muito interessante e útil.
Penelope #
5
@ArtOfWarfare Não sei se concordo que seja um hack ruim, Rose tem uma ótima solução, mas envolve digitar func ((type []) {val1, val2, 0}); que parece desajeitado, e se você tivesse #define func_short_cut (...) func ((type []) { VA_ARGS }); então você pode simplesmente chamar func_short_cut (1, 2, 3, 4, 0); que lhe dá a mesma sintaxe que uma função variadic normal, com o benefício adicional do puro truque de Rose ... qual é o problema aqui?
chrispepper1989
9
E se você quiser passar 0 como argumento?
Julian Gold
11
Isso exige que seus usuários lembrem-se de ligar com um final de 0. Como é mais seguro?
cp.engr 26/02
29

No magnífico C ++ 0x, você pode usar modelos variados:

template <typename ... Ts>
void format_string(char *fmt, Ts ... ts) {}

template <typename ... Ts>
void debug_print(int dbg_lvl, char *fmt, Ts ... ts)
{
  format_string(fmt, ts...);
}
user2023370
fonte
Não se esqueça que os modelos variados ainda não estão disponíveis no Visual Studio ... isso pode não lhe interessar, é claro!
Tom Swirly
11
Se você estiver usando o Visual Studio, modelos variados poderão ser adicionados ao Visual Studio 2012 usando o CTP de novembro de 2012. Se você estiver usando o Visual Studio 2013, terá modelos variados.
user2023370
7

Você pode usar montagem embutida para a chamada de função. (neste código, presumo que os argumentos sejam caracteres).

void format_string(char *fmt, ...);
void debug_print(int dbg_level, int numOfArgs, char *fmt, ...)
    {
        va_list argumentsToPass;
        va_start(argumentsToPass, fmt);
        char *list = new char[numOfArgs];
        for(int n = 0; n < numOfArgs; n++)
            list[n] = va_arg(argumentsToPass, char);
        va_end(argumentsToPass);
        for(int n = numOfArgs - 1; n >= 0; n--)
        {
            char next;
            next = list[n];
            __asm push next;
        }
        __asm push fmt;
        __asm call format_string;
        fprintf(stdout, fmt);
    }
Yoda
fonte
4
Não é portátil, depende do compilador e impede a otimização do compilador. Solução muito ruim.
Geoffroy
4
Novo sem excluir também.
user7116
8
Pelo menos isso realmente responde à pergunta, sem redefinir a pergunta.
Lama12345 25/11
6

Você pode tentar macro também.

#define NONE    0x00
#define DBG     0x1F
#define INFO    0x0F
#define ERR     0x07
#define EMR     0x03
#define CRIT    0x01

#define DEBUG_LEVEL ERR

#define WHERESTR "[FILE : %s, FUNC : %s, LINE : %d]: "
#define WHEREARG __FILE__,__func__,__LINE__
#define DEBUG(...)  fprintf(stderr, __VA_ARGS__)
#define DEBUG_PRINT(X, _fmt, ...)  if((DEBUG_LEVEL & X) == X) \
                                      DEBUG(WHERESTR _fmt, WHEREARG,__VA_ARGS__)

int main()
{
    int x=10;
    DEBUG_PRINT(DBG, "i am x %d\n", x);
    return 0;
}
GeekyJ
fonte
6

Embora você possa resolver passar o formatador armazenando-o no buffer local primeiro, mas isso precisa ser empilhado e pode ser um problema para resolver. Eu tentei seguir e parece funcionar bem.

#include <stdarg.h>
#include <stdio.h>

void print(char const* fmt, ...)
{
    va_list arg;
    va_start(arg, fmt);
    vprintf(fmt, arg);
    va_end(arg);
}

void printFormatted(char const* fmt, va_list arg)
{
    vprintf(fmt, arg);
}

void showLog(int mdl, char const* type, ...)
{
    print("\nMDL: %d, TYPE: %s", mdl, type);

    va_list arg;
    va_start(arg, type);
    char const* fmt = va_arg(arg, char const*);
    printFormatted(fmt, arg);
    va_end(arg);
}

int main() 
{
    int x = 3, y = 6;
    showLog(1, "INF, ", "Value = %d, %d Looks Good! %s", x, y, "Infact Awesome!!");
    showLog(1, "ERR");
}

Espero que isto ajude.

VarunG
fonte
2

A solução de Ross limpou um pouco. Só funciona se todos os argumentos forem ponteiros. Além disso, a implementação da linguagem deve oferecer suporte à exclusão da vírgula anterior, se __VA_ARGS__estiver vazia (o Visual Studio C ++ e o GCC).

// pass number of arguments version
 #define callVardicMethodSafely(...) {value_t *args[] = {NULL, __VA_ARGS__}; _actualFunction(args+1,sizeof(args) / sizeof(*args) - 1);}


// NULL terminated array version
 #define callVardicMethodSafely(...) {value_t *args[] = {NULL, __VA_ARGS__, NULL}; _actualFunction(args+1);}
BSalita
fonte
0

Digamos que você tenha uma função variável típica que você escreveu. Como pelo menos um argumento é necessário antes do variável ..., você sempre deve escrever um argumento extra em uso.

Ou você?

Se você agrupar sua função variável em uma macro, não precisará de um argumento anterior. Considere este exemplo:

#define LOGI(...)
    ((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))

Isso é obviamente muito mais conveniente, pois você não precisa especificar o argumento inicial todas as vezes.

Engenheiro
fonte
-5

Não tenho certeza se isso funciona para todos os compiladores, mas funcionou até agora para mim.

void inner_func(int &i)
{
  va_list vars;
  va_start(vars, i);
  int j = va_arg(vars);
  va_end(vars); // Generally useless, but should be included.
}

void func(int i, ...)
{
  inner_func(i);
}

Você pode adicionar o ... ao inner_func () se quiser, mas não precisa. Funciona porque o va_start usa o endereço da variável especificada como ponto de partida. Nesse caso, estamos fazendo referência a uma variável em func (). Portanto, ele usa esse endereço e lê as variáveis ​​depois na pilha. A função inner_func () está lendo o endereço da pilha de func (). Portanto, ele só funciona se as duas funções usarem o mesmo segmento de pilha.

As macros va_start e va_arg geralmente funcionarão se você fornecer algum var como ponto de partida. Então, se você quiser, pode passar ponteiros para outras funções e usá-las também. Você pode criar suas próprias macros com bastante facilidade. Tudo o que as macros fazem é digitar endereços de memória. No entanto, fazê-los funcionar para todos os compiladores e convenções de chamada é irritante. Portanto, geralmente é mais fácil usar os que acompanham o compilador.

Jim
fonte