【问题标题】:Adding extra arguments to a C-style variadic argument list向 C 风格的可变参数列表添加额外的参数
【发布时间】:2013-10-15 17:23:51
【问题描述】:

我正在尝试为 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 不会神奇地变成 vprintf 所期望的 va_list

我知道我可以编写一个宏并使用__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 可变参数模板。)

【问题讨论】:

  • 你已经看过这里了吗:stackoverflow.com/a/150616/2036917
  • @UdoKlein:这些问题只是建议使用va_list。我已经知道了,问题是我需要为其添加参数,但我不知道该怎么做。
  • 我真的不明白您为什么需要使用单个 vprintf 语句。据我所知,wrapper 的额外参数也只是要打印正确的变量吗?为什么你不能使用单独的打印语句,或者最好只是以wrapper 调用的格式包含它们?
  • @Pankrates:我包装的实际函数不是vprintf,它不具有两个连续调用与使用组合参数的单个调用具有相同效果的属性。跨度>
  • 一般情况下,您不能将 args 添加到 va_list。它是一个不透明的类型,它支持的唯一操作是 va_start 来初始化它, va_end 来表明你已经完成了它(也许是 va_copy)。现在,如果你找到了 va_start 的实现,也许你可以想出一个聪明的方法来添加 args,但这可能会破坏保证:)

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


【解决方案1】:

我看不出使用宏有什么问题,但这就是我的问题:)

无论如何,有办法做到这一点。 如果您不关心可移植性,则可以使用内联汇编。 可变参数函数使用 cdecl 调用约定,这意味着调用者负责将参数压入堆栈,然后在被调用者返回后清理堆栈。 因此,一个简单的 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);

这是相当麻烦且不可移植的解决方案,但它应该可以工作。

希望有帮助!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多