在C风格的可变参数列表中添加额外的参数

时间:2013-10-15 17:23:51

标签: c++ c variadic-functions variadic-macros

我正在尝试为像printf这样的C风格的可变参数函数编写一个包装器,这会增加一些额外的参数,但是我遇到了麻烦:

void printf(char* fmt, ...);  // the thing I'm trying to wrap

void wrapper(char* fmt, ...)
{
    printf(fmt, extra_arg1, extra_arg2, /* the variadic arguments */);
}

但是我为/* the variadic arguments */写了什么?

即使我试图包装的函数有一个版本需要va_list,我也不能这样做:

void vprintf(char* fmt, va_list args);

void wrapper(char* fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    vprintf(fmt, extra_arg1, extra_arg2, args);
    va_end(args);
}

extra_arg1extra_arg2args不会神奇地变成va_list所期望的vprintf

我知道我可以写一个宏并使用__VA_ARGS__

void printf(char* fmt, ...);  // the thing I'm trying to wrap

#define wrapper(fmt, ...) printf(fmt, extra_arg1, extra_arg2, __VA_ARGS__)

但我试图避免这种情况,并将包装器作为函数编写。有没有办法做到这一点?

(顺便说一下,我也不能使用C ++ 11可变参数模板。)

1 个答案:

答案 0 :(得分:0)

我认为使用宏没有任何问题,但那只是我:)

无论如何,有一种方法可以做到这一点。 如果您不关心可移植性,可以使用内联汇编。 变量函数使用cdecl调用convension,这意味着调用者负责将参数推送到堆栈而不是在被调用者返回后清理堆栈。 因此,一个简单的printf调用

printf("%d, %d, %d", 1, 2, 3)

看起来像:

char format[] = "%d, %d, %d";
__asm {
  push 3
  push 2
  push 1
  lea  eax, [format]
  push eax
  call printf
  add  esp,10h
}

请注意,va_start使用推送的最后一个参数来获取堆栈上的地址,而va_arg只是遍历参数。

您可以使用相同的行为并再次推送可变参数:

char new_format[] = "%d, %d, %d, %d, %d";
wrapper(new_format, ...) {
  char *va = addressof(new_format)+sizeof(char*);  // first argument after new_format
  int i;

  __asm {
    push extra2
    push extra1
  }
  for(i=0; i < num_of_arguments; i++)
     __asm push, va += sizeof(argument)
  // we just pushed [3, 2, 1] in our example
  __asm {
    lea  eax, [new_format]
    push eax
    call printf
    add  esp,18h   // <-- We now have to discard more stuff on the stack
  }
}

请注意,您必须事先知道堆栈上有多少个参数以及每个参数的大小(printf从格式字符串中计算出来)。

你可以模仿printf并从格式字符串(或你正在包装的任何其他函数)中提取相关信息。

现在,如果您对类型没有任何了解,您仍然可以逃脱它 通过在堆栈上为变量腾出空间并复制内存(从堆栈到堆栈):

__asm {
  push extra2
  push extra1
  sub esp, total-bytes-needed
}
memcpy(esp, block-of-memory, size-of-block);

这是相当麻烦且不便于携带的解决方案,但应该可行。

希望有所帮助!