Przekazywanie zmiennych argumentów do innej funkcji, która akceptuje listę argumentów zmiennych


114

Więc mam 2 funkcje, które mają podobne argumenty

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

Teraz examplewywołuje exampleB, ale jak mogę przekazać zmienne na liście argumentów zmiennych bez modyfikowania exampleB(ponieważ jest to już używane w innym miejscu).



2
Cóż, rozwiązaniem na tym było użycie vprintf, a tak nie jest w tym przypadku.
Niedostępne

Jest to związane, ale na pewno nie to samo, co proponowany duplikat: Przekaż wywołanie funkcji wariadycznej w C?
Jonathan Leffler

Odpowiedzi:


127

Nie możesz tego zrobić bezpośrednio; musisz stworzyć funkcję, która przyjmuje 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
Podejrzewam, że musiałem zrobić coś takiego, problem polega na tym, że przykładowa funkcja jest w zasadzie opakowaniem dla vsprintf i niewiele więcej: /
Niedostępne

@Xeross: Zwróć uwagę, że nie zmienia to zewnętrznej specyfikacji tego, co robi przykładB - zmienia tylko wewnętrzną implementację. Nie jestem pewien, na czym polega problem.
Jonathan Leffler

Czy pierwszy parametr jest wymagany, czy też wszystkie parametry mogą być wariadyczne?
Qwerty

1
@Qwerty: Składnia wymaga nazwanego pierwszego argumentu funkcji, która przyjmuje zmienną listę argumentów z , ...podpisem. Jeśli przekonwertowałeś zmienne argumenty na a va_list, możesz przekazać va_listinną funkcję, która przyjmuje tylko a va_list, ale ta funkcja (lub taka, którą wywołuje) musi mieć jakiś sposób na sprawdzenie, co jest w va_list.
Jonathan Leffler

46

Może tutaj wrzucisz kamień do stawu, ale wydaje się, że działa całkiem nieźle z szablonami wariadycznymi 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);
}

Przynajmniej działa z g++.


Jestem zdezorientowany - czy jest to uzasadnione podejście przy użyciu C ++> = 11?
Duncan Jones

@DuncanJones Tak, pakiet zostaje rozszerzony oargs...
Swordfish

15

powinieneś stworzyć wersje tych funkcji, które pobierają va_list i przekazują je. Spójrz na vprintfprzykład:

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

5

Chciałem też zawinąć printf i znalazłem tutaj pomocną odpowiedź:

Jak przekazać zmienną liczbę argumentów do printf / sprintf

W ogóle nie byłem zainteresowany wydajnością (jestem pewien, że ten fragment kodu można ulepszyć na wiele sposobów, nie krępuj się :)), to jest tylko do ogólnego debugowania, więc zrobiłem to:

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

których następnie mogę użyć w ten sposób

Point2d p;

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

C ++ ostreams są piękne w niektórych aspektach, ale praktycznie stają się przerażające, jeśli chcesz wydrukować coś takiego z kilkoma małymi ciągami, takimi jak nawiasy, dwukropki i przecinki wstawione między liczby.


2

Nawiasem mówiąc, wiele implementacji C ma wewnętrzną odmianę v? Printf, która IMHO powinna być częścią standardu C. Dokładne szczegóły są różne, ale typowa implementacja akceptuje strukturę zawierającą wskaźnik funkcji wyjściowej znakowej i informację mówiącą, co ma się stać. Dzięki temu printf, sprintf i fprintf mogą używać tego samego mechanizmu „rdzenia”. Na przykład vsprintf może wyglądać następująco:

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

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

Następnie funkcja core_printf wywołuje p_inf-> func dla każdego znaku do wyprowadzenia; funkcja wyjściowa może następnie wysłać znaki do konsoli, pliku, ciągu lub czegoś innego. Jeśli implementacja ujawnia funkcję core_printf (i jakikolwiek mechanizm konfiguracji, którego używa), można ją rozszerzyć o różne rodzaje odmian.


1

Możliwym sposobem jest użycie #define:

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

0

Opierając się na komentarzu, który pakujesz vsprintfi który jest oznaczony jako C ++, sugerowałbym, aby nie próbować tego robić, ale zmień interfejs, aby zamiast tego używał C ++ iostreams. Mają zalety w stosunku do printlinii funkcji, takich jak bezpieczeństwo typów i możliwość drukowania elementów, printfktóre nie byłyby w stanie obsłużyć. Niektóre poprawki teraz mogą zaoszczędzić znacznej ilości bólu w przyszłości.


O jakich zaletach Pan mówi?
cjcurrie

@cjcurrie: zaletą jest bezpieczeństwo typów, nawet w przypadku typów zdefiniowanych przez użytkownika. Oczywiście funkcje C w ogóle nie obsługują typów zdefiniowanych przez użytkownika.
Jonathan Leffler

-1

Korzystając z nowego standardu C ++ 0x, możesz to zrobić za pomocą szablonów wariadycznych, a nawet przekonwertować stary kod na nową składnię szablonu bez przerywania czegokolwiek.


niestety nie jest to możliwe we wszystkich przypadkach - po prostu spróbuj użyć
lambd

-1

To jedyny sposób, aby to zrobić ... i najlepszy sposób, aby to zrobić.

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;
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.