Передача переменных аргументов другой функции, которая принимает список переменных аргументов


114

Итак, у меня есть 2 функции с одинаковыми аргументами.

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

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



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

Это связано с предложенным дубликатом, но определенно не то же самое: Переслать вызов вариативной функции в C?
Джонатан Леффлер

Ответы:


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

С ++ ostreams красивы в некоторых аспектах, но на практике они становятся ужасными, если вы хотите напечатать что-то подобное с небольшими строками, такими как круглые скобки, двоеточия и запятые, вставленные между числами.


2

Между прочим, многие реализации C имеют внутреннюю вариацию v? Printf, которая, IMHO, должна была быть частью стандарта C. Точные детали различаются, но типичная реализация будет принимать структуру, содержащую указатель на функцию вывода символов и информацию о том, что должно произойти. Это позволяет printf, sprintf и fprintf использовать один и тот же «основной» механизм. Например, 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 args)
{
  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 ++, я бы предложил не пытаться это сделать, а изменить свой интерфейс, чтобы вместо этого использовать iostreams 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.