Передача змінних аргументів іншій функції, яка приймає список змінних аргументів


114

Тож у мене є 2 функції, які мають обидві аналогічні аргументи

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

Тепер exampleдзвінки exampleB, але як я можу передавати змінні у списку аргументів змінної без зміни exampleB(оскільки це вже використовується і в інших місцях).


6
можливий дублікат
переадресації

2
Що ж, рішення для цього використовувало vprintf, і це не так.
Не доступно

Це пов’язано із запропонованим дублікатом, але, безумовно, не таким, як: Посилати виклик варіативної функції в С?
Джонатан Леффлер

Відповіді:


127

Ви не можете це зробити безпосередньо; ви повинні створити функцію, яка займає 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
Підозрюється, що я повинен був зробити щось подібне, проблема в тому, що приклад функція - це в основному обгортка для vsprintf і не багато іншого: /
Недоступно

@Xeross: Зауважте, що це не змінює зовнішню специфікацію того, що робить exampleB - це лише змінює внутрішню реалізацію. Я не впевнений, у чому проблема.
Джонатан Леффлер

Чи потрібен перший параметр чи всі параметри можуть бути варіативними?
Qwerty

1
@Qwerty: Синтаксис вимагає іменного першого аргументу для функції, яка приймає змінний список аргументів з , ...підписом. Якщо ви перетворили аргументи змінної в a va_list, ви можете передати va_listіншу функцію, яка бере лише a va_list, але ця функція (або та, яку вона викликає) повинна мати певний спосіб знати, що знаходиться в va_list.
Джонатан Леффлер

46

Можливо, киньте скелю у ставку, але, здається, це працює нормально із різноманітними шаблонами 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);
}

Принаймні, це працює з g++.


Я розгублений - це законний підхід із використанням C ++> = 11?
Данкан Джонс

@DuncanJones Так, пакет розширюється наargs...
Swordfish

15

ви повинні створити версії цих функцій, які беруть список va_list і передають їх. Подивіться на vprintfприклад:

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

5

Я також хотів обернути printf і знайшов тут корисну відповідь:

Як передавати змінну кількість аргументів printf / sprintf

Мене зовсім не цікавила продуктивність (я впевнений, що цей фрагмент коду можна вдосконалити кількома способами, не соромтеся це робити :)), це лише для загальної налагодження друку, тому я це зробив:

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

яку я потім можу використовувати так

Point2d p;

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

Потоки c ++ в деяких аспектах прекрасні, але практично стають жахливими, якщо ви хочете надрукувати щось подібне за допомогою невеликих рядків, таких як дужки, колонки та коми, вставлені між числами.


2

До речі, у багатьох реалізаціях C є внутрішня варіація v? Printf, яка IMHO повинна була бути частиною стандарту C. Точні деталі різняться, але типова реалізація прийме структуру, що містить покажчик функції виводу символів та інформацію, яка говорить про те, що повинно відбутися. Це дозволяє printf, sprintf і fprintf всім використовувати один і той же механізм "core". Наприклад, vsprintf може бути на кшталт:

void s_out (PRINTF_INFO * p_inf, char ch)
{
  (* (p_inf-> destptr) ++) = ch;
  p_inf-> результат ++;
}

int vsprintf (char * dest, const char * fmt, аргументи va_list)
{
  PRINTF_INFO p_inf;
  p_inf.destptr = dest;
  p_inf.result = 0;
  p_inf.func = s_out;
  core_printf (& p_inf, fmt, args);
}

Потім функція core_printf викликає p_inf-> func для виведення кожного символу; Функція виводу може потім надіслати символи до консолі, файлу, рядка чи чогось іншого. Якщо реалізація виявляє функцію core_printf (і який би механізм установки він не використовував), можна розширити її різними варіантами.


1

Можливий спосіб - використовувати #define:

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

0

Виходячи з коментаря, який ви завершуєте vsprintf, і що він позначений як C ++, я б пропонував не намагатися цього робити, а змінити свій інтерфейс, щоб використовувати замість нього ідентифікатори C ++. Вони мають переваги перед printрядом функцій, такими як безпека типу та можливість друку предметів, з якими printfне можна було б працювати. Деякі переробки зараз можуть врятувати значну кількість болю в майбутньому.


На які переваги ви посилаєтесь?
cjcurrie

@cjcurrie: перевага - безпека типу, навіть із визначеними користувачем типами. Звичайно, функції C не можуть обробляти визначені користувачем типи.
Джонатан Леффлер

-1

Використовуючи новий стандарт C ++ 0x, ви можете це зробити за допомогою різних шаблонів або навіть перетворити цей старий код у новий синтаксис шаблону, нічого не порушуючи.


на жаль, це неможливо у всіх випадках - просто спробуйте використовувати лямбдаси
serup

-1

Це єдиний спосіб зробити це .. і найкращий спосіб зробити це теж ..

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;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.