转发C中可变参数函数的调用
在C语言中,是否可以转发可变参数函数的调用?就像
int my_printf(char *fmt, ...) {
fprintf(stderr, "Calling printf with fmt %s", fmt);
return SOMEHOW_INVOKE_LIBC_PRINTF;
}
在这种情况下,显然不必严格按照上述方式转发调用(因为我们可以通过其他方式记录调用,或者使用vfprintf),但是我正在使用的代码库要求包装程序执行一些实际的工作,并且没有(也没有增加)类似于vfprintf的辅助函数。
[更新:根据到目前为止提供的答案,似乎有些困惑。用另一种方式来表达这个问题:通常,我们可以在不修改函数定义的情况下包装任意可变参数函数吗?
解决方案
使用vfprintf:
int my_printf(char *fmt, ...) {
va_list va;
int ret;
va_start(va, fmt);
ret = vfprintf(stderr, fmt, va);
va_end(va);
return ret;
}
几乎,使用<stdarg.h>中提供的功能:
#include <stdarg.h>
int my_printf(char *format, ...)
{
va_list args;
va_start(args, format);
int r = vprintf(format, args);
va_end(args);
return r;
}
注意,我们将需要使用vprintf版本而不是普通的printf版本。在这种情况下,没有一种方法可以不使用va_list来直接调用可变参数函数。
无法转发此类函数调用,因为可以检索原始堆栈元素的唯一位置是在" my_print()"中。包装这样的调用的通常方法是具有两个函数,一个仅将参数转换为各种varargs结构,而另一个实际上对这些结构进行操作。使用这样的双重功能模型,我们可以(例如)通过用va_start()初始化my_printf()中的结构,然后将它们传递给vfprintf(),来包装printf()。
如果我们没有类似于vfprintf的函数,而该函数却需要一个va_list而不是可变数量的参数,那么我们就不能这样做。请参阅http://c-faq.com/varargs/handoff.html。
例子:
void myfun(const char *fmt, va_list argp) {
vfprintf(stderr, fmt, argp);
}
可变参数函数与varargs风格的替代函数成对出现,这不是直接的,但是很普遍(在标准库中,我们几乎会普遍找到这种情况)。例如printf/vprintf
v ...函数采用va_list参数,通常通过特定于编译器的"宏魔术"来实现该参数,但是可以确保从像这样的可变参数函数中调用v ... style函数可以正常工作:
#include <stdarg.h>
int m_printf(char *fmt, ...)
{
int ret;
/* Declare a va_list type variable */
va_list myargs;
/* Initialise the va_list variable with the ... after fmt */
va_start(myargs, fmt);
/* Forward the '...' to vprintf */
ret = vprintf(fmt, myargs);
/* Clean up the va_list */
va_end(myargs);
return ret;
}
这应该给我们想要的效果。
如果我们正在考虑编写可变参数的库函数,则还应考虑使va_list样式的同伴作为库的一部分可用。正如我们从问题中看到的那样,它可以证明对用户有用。
C99支持带有可变参数的宏;根据编译器,我们也许可以声明一个可以执行所需操作的宏:
#define my_printf(format, ...) \
do { \
fprintf(stderr, "Calling printf with fmt %s\n", format); \
some_other_variadac_function(format, ##__VA_ARGS__); \
} while(0)
通常,最好的解决方案是使用要包装的函数的va_list形式(如果存在的话)。
抱歉,话题不在话下,但:
元问题是,C语言中的varargs接口从一开始就已经从根本上被破坏了。这是对缓冲区溢出和无效内存访问的邀请,因为如果没有显式的结束信号(没有人真正出于懒惰而使用),则无法找到参数列表的末尾。而且,它始终依赖于深奥的实现特定宏,而重要的va_copy()宏仅在某些体系结构上受支持。
是的,我们可以执行此操作,但是它有点难看,我们必须知道最大数量的参数。此外,如果我们所处的体系结构没有像x86那样在堆栈上传递参数(例如PowerPC),则必须知道是否使用了"特殊"类型(双精度,浮点型,altivec等),以及是否因此,请相应地处理它们。可能很快会很痛苦,但是如果我们使用的是x86,或者原始功能的边界明确且受限制,则可以正常使用。
仍然会是一个hack,将其用于调试目的。不要围绕此构建软件。
无论如何,这是x86上的一个有效示例:
#include <stdio.h>
#include <stdarg.h>
int old_variadic_function(int n, ...)
{
va_list args;
int i = 0;
va_start(args, n);
if(i++<n) printf("arg %d is 0x%x\n", i, va_arg(args, int));
if(i++<n) printf("arg %d is %g\n", i, va_arg(args, double));
if(i++<n) printf("arg %d is %g\n", i, va_arg(args, double));
va_end(args);
return n;
}
int old_variadic_function_wrapper(int n, ...)
{
va_list args;
int a1;
int a2;
int a3;
int a4;
int a5;
int a6;
int a7;
int a8;
/* Do some work, possibly with another va_list to access arguments */
/* Work done */
va_start(args, n);
a1 = va_arg(args, int);
a2 = va_arg(args, int);
a3 = va_arg(args, int);
a4 = va_arg(args, int);
a5 = va_arg(args, int);
a6 = va_arg(args, int);
a7 = va_arg(args, int);
va_end(args);
return old_variadic_function(n, a1, a2, a3, a4, a5, a6, a7, a8);
}
int main(void)
{
printf("Call 1: 1, 0x123\n");
old_variadic_function(1, 0x123);
printf("Call 2: 2, 0x456, 1.234\n");
old_variadic_function(2, 0x456, 1.234);
printf("Call 3: 3, 0x456, 4.456, 7.789\n");
old_variadic_function(3, 0x456, 4.456, 7.789);
printf("Wrapped call 1: 1, 0x123\n");
old_variadic_function_wrapper(1, 0x123);
printf("Wrapped call 2: 2, 0x456, 1.234\n");
old_variadic_function_wrapper(2, 0x456, 1.234);
printf("Wrapped call 3: 3, 0x456, 4.456, 7.789\n");
old_variadic_function_wrapper(3, 0x456, 4.456, 7.789);
return 0;
}
由于某些原因,我们不能将浮点数与va_arg一起使用,gcc表示它们会转换为double值,但程序会崩溃。仅此一点就表明该解决方案是黑客,并且没有通用解决方案。
在我的示例中,我假设参数的最大数量为8,但是我们可以增加该数量。包装函数也仅使用整数,但它与其他"常规"参数的工作方式相同,因为它们始终转换为整数。目标函数将知道它们的类型,但中间包装器不需要。包装程序也不需要知道正确数量的参数,因为目标函数也可以知道。
为了进行有用的工作(除了仅记录呼叫),我们可能必须同时了解两者。

