【问题标题】:Passing variable arguments to another function that accepts a variable argument list将可变参数传递给另一个接受可变参数列表的函数
【发布时间】:2011-04-01 15:22:20
【问题描述】:

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

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

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

【问题讨论】:

标签: c++ variadic-functions


【解决方案1】:

你不能直接做;你必须创建一个接受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...
}

【讨论】:

  • 怀疑我不得不做这样的事情,问题是示例函数基本上是 vsprintf 的包装器,而不是其他的:/
  • @Xeross:请注意,这不会改变 exampleB 所做的外部规范——它只是改变了内部实现。我不确定是什么问题。
  • 第一个参数是必须的还是所有参数都可以是可变参数?
  • @Qwerty:该语法需要函数的命名第一个参数,该函数采用签名中带有, ... 的变量参数列表。如果您已将变量参数转换为va_list,则可以将va_list 传递给另一个只接受va_list 的函数,但该函数(或它调用的函数)必须有某种方式知道其中的内容va_list.
【解决方案2】:

也许在这里往池塘里扔一块石头,但它似乎与 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++

【讨论】:

【解决方案3】:

您应该创建带有 va_list 的这些函数的版本,并传递它们。以vprintf为例:

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

【讨论】:

    【解决方案4】:

    我也想包装 printf 并在这里找到了一个有用的答案:

    How to pass variable number of arguments to 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++ ostreams 在某些方面很漂亮,但如果你想打印这样的东西,在数字之间插入一些小字符串,如括号、冒号和逗号,那实际上就变得可怕了。

    【讨论】:

      【解决方案5】:

      一种可能的方法是使用#define:

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

      【讨论】:

        【解决方案6】:

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

        void 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 函数(以及它使用的任何设置机制),则可以使用各种变体对其进行扩展。

        【讨论】:

          【解决方案7】:

          根据您包装vsprintf 的评论,并且这被标记为C++,我建议不要尝试这样做,而是更改您的界面以使用C++ iostreams。它们比print 功能线具有优势,例如类型安全以及能够打印printf 无法处理的项目。现在的一些返工可以在未来减少大量的痛苦。

          【讨论】:

          • 你指的是什么优势?
          • @cjcurrie:优点是类型安全,即使是用户定义的类型。当然,C 函数根本无法处理用户定义的类型。
          【解决方案8】:

          这可能与此处描述的情况不完全相同,但如果您要为字符串格式函数(例如记录器)定义包装器:

          void logger(const char *name, const char *format, ...);
          void wrapper(const char *format, ...);
          

          当您实现一个调用loggerwrapper 时,我们可以先用vasprintf 创建一个字符串,然后将其传递给logger

          #include <stdio.h>
          #include <stdlib.h>
          #include <stdarg.h>
          
          static void wrapper(const char *format, ...)
          {
              char *string;
              va_list args;
              va_start(args, format);
          
              // variadic printf with allocated string. must free()
              vasprintf(&string, format, args);
              logger("wrapper", "%s", string);
          
              free(string);
              va_end(args);
          }
          

          不是最干净的,但有效。当您必须避免使用 macro 函数时,请尝试此操作。

          【讨论】:

            【解决方案9】:

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

            【讨论】:

            • 不幸的是,这并非在所有情况下都是可能的 - 只需尝试使用 lambdas
            【解决方案10】:

            这是唯一的方法..也是最好的方法..

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

            【讨论】:

            • 为什么会有人给我投反对票,这实际上是最好的方法,它使用循环将所有参数推送到堆栈中,然后所有推送的推送都由 call eax 调用是OriginalVarArgumentsFunction 地址,这样您就可以使用... 与尽可能多的参数,并且仍然可以在挂钩中使用它。
            • 你听说有不是i386的处理器吗?
            • 为什么会有人挂钩像 va_ 这样的 CRT 函数?另外,您是否意识到如果使用 /MT 编译,这是没有用的?另外,您是否意识到在 x64 上没有内联 ASM?也......不......只是不是一个好的答案。
            • 没有办法为随机变量大小的参数生成...,这是唯一没有办法解决这个问题的方法..我已经测试过..你不能简单地调用这样的东西@ 987654326@ 这就是这个修复的原因,解决了所有问题。
            猜你喜欢
            • 2018-10-22
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多