Passer des arguments de variable à une autre fonction qui accepte une liste d'arguments de variable

114

J'ai donc 2 fonctions qui ont toutes deux des arguments similaires

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

Maintenant exampleappelle exampleB, mais comment puis - je transmettre les variables dans la liste d'arguments variable sans modifier exampleB(comme cela est déjà utilisé ailleurs aussi).

Indisponible
la source
6
duplication possible de Transférer une invocation d'une fonction variadique en C
Greg Hewgill
2
Eh bien, la solution à ce sujet utilisait vprintf, et ce n'est pas le cas ici.
Indisponible
Ceci est lié, mais certainement pas le même que, le doublon proposé: Transférer une invocation d'une fonction variadique en C?
Jonathan Leffler

Réponses:

127

Vous ne pouvez pas le faire directement; vous devez créer une fonction qui prend un 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...
}
Jonathan Leffler
la source
2
Je suppose que je devais faire quelque chose comme ça, le problème est que la fonction d'exemple est essentiellement un wrapper pour vsprintf et pas grand chose d'autre: /
Non disponible
@Xeross: Notez que cela ne change pas la spécification externe de ce que fait exampleB - cela change juste l'implémentation interne. Je ne sais pas quel est le problème.
Jonathan Leffler
Le premier paramètre est-il obligatoire ou tous les paramètres peuvent-ils être variadiques?
Qwerty le
1
@Qwerty: La syntaxe nécessite un premier argument nommé pour une fonction qui prend une liste d'arguments variables avec , ...dans la signature. Si vous avez converti les arguments de variable en a va_list, vous pouvez passer le va_listà une autre fonction qui ne prend que a va_list, mais cette fonction (ou celle qu'elle appelle) doit avoir un moyen de savoir ce qu'il y a dans le va_list.
Jonathan Leffler le
46

Peut-être jeter un rocher dans un étang ici, mais cela semble fonctionner assez bien avec les modèles variadiques 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);
}

À tout le moins, cela fonctionne avec g++.

Vincent Fourmond
la source
Je suis confus - est-ce une approche légitime utilisant C ++> = 11?
Duncan Jones
@DuncanJones Oui, le pack est élargi deargs...
Swordfish
15

vous devez créer des versions de ces fonctions qui prennent une va_list et les transmettre. Regardez vprintfcomme un exemple:

int vprintf ( const char * format, va_list arg );
dix quatre
la source
5

Je voulais également envelopper printf et j'ai trouvé une réponse utile ici:

Comment passer un nombre variable d'arguments à printf / sprintf

Je n'étais pas du tout intéressé par les performances (je suis sûr que ce morceau de code peut être amélioré de plusieurs façons, n'hésitez pas à le faire :)), c'est pour le débogage général uniquement, j'ai donc fait ceci:

//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 je peux ensuite utiliser comme ça

Point2d p;

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

Les ostreams c ++ sont beaux à certains égards, mais ils deviennent pratiquement horribles si vous voulez imprimer quelque chose comme ça avec quelques petites chaînes telles que des parenthèses, des deux points et des virgules insérés entre les nombres.

Axel
la source
2

Incidemment, de nombreuses implémentations C ont une variation interne v? Printf dont IMHO aurait dû faire partie de la norme C. Les détails exacts varient, mais une implémentation typique acceptera une structure contenant un pointeur de fonction de sortie de caractère et des informations indiquant ce qui est censé se produire. Cela permet à printf, sprintf et fprintf d'utiliser tous le même mécanisme «principal». Par exemple, vsprintf pourrait être quelque chose comme:

void s_out (PRINTF_INFO * p_inf, car ch)
{
  (* (p_inf-> destptr) ++) = ch;
  p_inf-> résultat ++;
}

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

La fonction core_printf appelle ensuite p_inf-> func pour chaque caractère à afficher; la fonction de sortie peut alors envoyer les caractères à la console, un fichier, une chaîne ou autre chose. Si une implémentation expose la fonction core_printf (et quel que soit le mécanisme de configuration qu'elle utilise), on peut l'étendre avec toutes sortes de variations.

supercat
la source
1

Une manière possible est d'utiliser #define:

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

Sur la base du commentaire que vous encapsulez vsprintfet que cela est étiqueté comme C ++, je suggérerais de ne pas essayer de le faire, mais de modifier votre interface pour utiliser les iostreams C ++ à la place. Ils présentent des avantages par rapport auprint gamme de fonctions, telles que la sécurité des caractères et la possibilité d'imprimer des éléments qui printfne pourraient pas être manipulés. Certaines retouches maintenant pourraient éviter une quantité importante de douleur à l'avenir.

Marque B
la source
De quels avantages parlez-vous?
cjcurrie
@cjcurrie: l'avantage est la sécurité des types, même avec des types définis par l'utilisateur. Les fonctions C ne peuvent pas du tout gérer les types définis par l'utilisateur, bien sûr.
Jonathan Leffler
-1

En utilisant le nouveau standard C ++ 0x, vous pourrez peut-être le faire en utilisant des modèles variadiques ou même convertir cet ancien code en la nouvelle syntaxe de modèle sans rien casser.

David
la source
malheureusement, ce n'est pas possible dans tous les cas - essayez simplement d'utiliser des lambdas
serup
-1

C'est la seule façon de le faire .. et la meilleure façon de le faire aussi ..

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;
}
SSpoke
la source