Passando argumentos de variáveis ​​para outra função que aceita uma lista de argumentos de variáveis


114

Portanto, tenho 2 funções com argumentos semelhantes

void example(int a, int b, ...);
void exampleB(int b, ...);

Agora examplechama exampleB, mas como posso passar as variáveis ​​na lista de argumentos de variáveis ​​sem modificar exampleB(já que isso já é usado em outro lugar também).



2
Bem, a solução nesse caso era usar vprintf, e esse não é o caso aqui.
Não disponível em

Isso está relacionado, mas definitivamente não é o mesmo que, a duplicata proposta: Encaminhar uma invocação de uma função variada em C?
Jonathan Leffler

Respostas:


127

Você não pode fazer isso diretamente; você tem que criar uma função que leva a va_list:

#include <stdarg.h>

static void exampleV(int b, va_list args);

void exampleA(int a, int b, ...)    // Renamed for consistency
{
    va_list args;
    do_something(a);                // Use argument a somehow
    va_start(args, b);
    exampleV(b, args);
    va_end(args);
}

void exampleB(int b, ...)
{
    va_list args;
    va_start(args, b);
    exampleV(b, args);
    va_end(args);
}

static void exampleV(int b, va_list args)
{
    ...whatever you planned to have exampleB do...
    ...except it calls neither va_start nor va_end...
}

2
Suspeitei que eu tinha que fazer algo assim, o problema é que a função de exemplo é basicamente um wrapper para vsprintf e não muito mais: /
Não disponível em

@Xeross: Observe que isso não muda a especificação externa do que exampleB faz - ele apenas muda a implementação interna. Não tenho certeza de qual é o problema.
Jonathan Leffler

O primeiro parâmetro é necessário ou todos os parâmetros podem ser variáveis?
Qwerty

1
@Qwerty: A sintaxe requer um primeiro argumento nomeado para uma função que recebe uma lista de argumentos variáveis , ...na assinatura. Se você converteu os argumentos das variáveis ​​em a va_list, pode passar o va_listpara outra função que recebe apenas a va_list, mas essa função (ou uma que ela chama) deve ter alguma maneira de saber o que está dentro do va_list.
Jonathan Leffler

46

Talvez jogar uma pedra em um lago aqui, mas parece funcionar muito bem com modelos variáveis ​​C ++ 11:

#include <stdio.h>

template<typename... Args> void test(const char * f, Args... args) {
  printf(f, args...);
}

int main()
{
  int a = 2;
  test("%s\n", "test");
  test("%s %d %d %p\n", "second test", 2, a, &a);
}

No mínimo, funciona com g++.


Estou confuso - esta é uma abordagem legítima usando C ++> = 11?
Duncan Jones

@DuncanJones Sim, o pacote é expandido atéargs...
Swordfish

15

você deve criar versões dessas funções que pegam uma va_list e as aprovar. Veja vprintfcomo um exemplo:

int vprintf ( const char * format, va_list arg );

5

Eu também queria embrulhar printf e encontrei uma resposta útil aqui:

Como passar um número variável de argumentos para printf / sprintf

Eu não estava nem um pouco interessado em desempenho (tenho certeza que este pedaço de código pode ser melhorado de várias maneiras, fique à vontade para fazer isso :)), isso é apenas para depuração geral, então fiz o seguinte:

//Helper function
std::string osprintf(const char *fmt, ...)
{
    va_list args;
    char buf[1000];
    va_start(args, fmt);
    vsnprintf(buf, sizeof(buf), fmt, args );
    va_end(args);
    return buf;
}

que eu então posso usar assim

Point2d p;

cout << osprintf("Point2d: (%3i, %3i)", p.x, p.y);
instead of for example:
cout << "Point2d: ( " << setw(3) << p.x << ", " << p.y << " )";

Os ostreams C ++ são bonitos em alguns aspectos, mas praticamente se tornam horríveis se você quiser imprimir algo assim com algumas pequenas strings como parênteses, dois pontos e vírgulas inseridos entre os números.


2

A propósito, muitas implementações C têm uma variação interna v? Printf que IMHO deveria ter feito parte do padrão C. Os detalhes exatos variam, mas uma implementação típica aceitará uma estrutura contendo um ponteiro de função de saída de caractere e informações dizendo o que deve acontecer. Isso permite que printf, sprintf e fprintf usem o mesmo mecanismo de 'núcleo'. Por exemplo, vsprintf pode ser algo como:

void s_out (PRINTF_INFO * p_inf, char ch)
{
  (* (p_inf-> destptr) ++) = ch;
  p_inf-> resultado ++;
}

int vsprintf (char * dest, const char * fmt, va_list args)
{
  PRINTF_INFO p_inf;
  p_inf.destptr = dest;
  p_inf.result = 0;
  p_inf.func = s_out;
  core_printf (& p_inf, fmt, args);
}

A função core_printf então chama p_inf-> func para cada caractere a ser gerado; a função de saída pode então enviar os caracteres para o console, um arquivo, uma string ou outra coisa. Se a implementação de alguém expõe a função core_printf (e qualquer mecanismo de configuração que ela usa), pode-se estendê-la com todos os tipos de variações.


1

Uma maneira possível é usar #define:

#define exampleB(int b, ...)  example(0, b, __VA_ARGS__)

0

Com base no comentário que você está redigindo vsprintfe que está marcado como C ++, sugiro que não tente fazer isso, mas mude sua interface para usar iostreams C ++. Eles têm vantagens sobre a printlinha de funções, como segurança de digitação e capacidade de imprimir itens que printfnão dariam conta. Algum retrabalho agora pode evitar uma quantidade significativa de dor no futuro.


A que vantagens você se refere?
cjcurrie

@cjcurrie: a vantagem é a segurança de tipo, mesmo com tipos definidos pelo usuário. As funções C não podem lidar de forma alguma com tipos definidos pelo usuário, é claro.
Jonathan Leffler,

-1

Usando o novo padrão C ++ 0x, você pode conseguir fazer isso usando modelos variados ou até mesmo converter o código antigo para a nova sintaxe de modelo sem quebrar nada.


infelizmente, isso não é possível em todos os casos - tente usar lambdas
serup

-1

Esta é a única forma de o fazer .. e a melhor forma de o fazer também ..

static BOOL(__cdecl *OriginalVarArgsFunction)(BYTE variable1, char* format, ...)(0x12345678); //TODO: change address lolz

BOOL __cdecl HookedVarArgsFunction(BYTE variable1, char* format, ...)
{
    BOOL res;

    va_list vl;
    va_start(vl, format);

    // Get variable arguments count from disasm. -2 because of existing 'format', 'variable1'
    uint32_t argCount = *((uint8_t*)_ReturnAddress() + 2) / sizeof(void*) - 2;
    printf("arg count = %d\n", argCount);

    // ((int( __cdecl* )(const char*, ...))&oldCode)(fmt, ...);
    __asm
    {
        mov eax, argCount
        test eax, eax
        je noLoop
        mov edx, vl
        loop1 :
        push dword ptr[edx + eax * 4 - 4]
        sub eax, 1
        jnz loop1
        noLoop :
        push format
        push variable1
        //lea eax, [oldCode] // oldCode - original function pointer
        mov eax, OriginalVarArgsFunction
        call eax
        mov res, eax
        mov eax, argCount
        lea eax, [eax * 4 + 8] //+8 because 2 parameters (format and variable1)
        add esp, eax
    }
    return res;
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.