将变量参数传递给另一个接受变量参数列表的函数


114

所以我有两个函数都有相似的参数

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

现在example调用exampleB,但是如何在不修改的情况下传递变量参数列表中的变量exampleB(因为该变量已经在其他地方使用过)。



2
好了,那个人的解决方案是使用vprintf,这里不是这种情况。
不可用

这与拟议的副本有关,但绝对不相同:在C中转发可变参数函数的调用?
乔纳森·莱夫勒

Answers:


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:该语法要求函数的命名第一个参数采用, ...签名中包含可变参数列表的函数。如果您已将变量参数转换为va_list,则可以将传递va_list给仅使用a的另一个函数va_list,但是该函数(或它调用的函数)必须具有某种了解in中内容的方法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的合法方法吗?
Duncan Jones

@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 ++ ostream在某些方面很漂亮,但是如果您想用一些小字符串(例如括号,冒号和逗号插入数字之间)来打印这样的内容,这实际上会变得很可怕。


2

顺便说一下,许多C实现都有内部vprintf变体,恕我直言,IMHO应该是C标准的一部分。确切的细节有所不同,但是典型的实现将接受包含字符输出函数指针和说明将要发生的信息的结构。这允许printf,sprintf和fprintf都使用相同的“核心”机制。例如,vsprintf可能类似于:

无效s_out(PRINTF_INFO * p_inf,char ch)
{
  (*(p_inf-> destptr)++)= ch;
  p_inf-> result ++;
}

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 ++,我建议您不要尝试这样做,而是将您的接口更改为使用C ++ iostream。它们print在功能方面具有优势,例如类型安全和能够打印printf无法处理的项目。现在进行一些重做可以在将来节省大量的痛苦。


您指的是什么优势?
cjcurrie

@cjcurrie:优点是类型安全,即使使用用户定义的类型也是如此。当然,C函数根本无法处理用户定义的类型。
乔纳森·莱夫勒

-1

使用新的C ++ 0x标准,您可以使用可变参数模板完成此任务,甚至可以将旧代码转换为新模板语法而不会破坏任何内容。


不幸的是,这并非在所有情况下都可行-只需尝试使用lambdas
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;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.