在C中转发一个可变参数的调用

在C中,是否可以转发一个可变参数的调用? 如在,

int my_printf(char *fmt, ...) { fprintf(stderr, "Calling printf with fmt %s", fmt); return SOMEHOW_INVOKE_LIBC_PRINTF; } 

以上述方式转发调用明显不是必须的(因为你可以以其他方式logging调用,或者使用vfprintf),但是我正在处理的代码库需要包装器做一些实际的工作,没有(也不能添加)类似于vfprintf的帮助函数。

[更新:基于迄今为止提供的答案,似乎存在一些混淆。 用另一种方式来说这个问题:一般来说,你可以包装一些任意的可变参数而不修改该函数的定义

如果你没有一个类似vfprintf的函数,而不是va_list而不是可变数量的参数, 那么你就不能这样做 。 请参阅http://c-faq.com/varargs/handoff.html

例:

 void myfun(const char *fmt, va_list argp) { vfprintf(stderr, fmt, argp); } 

不是直接的,然而它是常见的(你会发现几乎普遍在标准库的情况下)可变参数函数与varargstypes的替代函数成对。 例如printf / vprintf

v …函数需要一个va_list参数,其实现通常是通过编译器特定的“macros魔法”来完成的,但是您可以保证,从像这样的可变参数函数中调用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样式随附资源作为库的一部分提供。 从您的问题中可以看出,它可以certificate您的用户有用。

C99支持可变参数的macros ; 取决于你的编译器,你可能可以声明一个你想要的macros:

 #define my_printf(format, ...) \ do { \ fprintf(stderr, "Calling printf with fmt %s\n", format); \ some_other_variadac_function(format, ##__VA_ARGS__); \ } while(0) 

一般来说,最好的解决scheme是使用你试图包装的函数的va_listforms,如果存在的话。

几乎使用<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情况下,没有办法在这种情况下直接调用可变参数函数。

由于不太可能以很好的方式转发这样的调用,所以我们通过用原始堆栈框架的副本设置新的堆栈框架来解决这个问题。 然而,这是非常不可移植的,并且做出各种假设 ,例如代码使用帧指针和“标准”调用约定。

这个头文件允许为x86_64和i386(GCC)包装可变参数函数。 它不适用于浮点参数,但应该直接扩展以支持这些参数。

 #ifndef _VA_ARGS_WRAPPER_H #define _VA_ARGS_WRAPPER_H #include <limits.h> #include <stdint.h> #include <alloca.h> #include <inttypes.h> #include <string.h> /* This macros allow wrapping variadic functions. * Currently we don't care about floating point arguments and * we assume that the standard calling conventions are used. * * The wrapper function has to start with VA_WRAP_PROLOGUE() * and the original function can be called by * VA_WRAP_CALL(function, ret), whereas the return value will * be stored in ret. The caller has to provide ret * even if the original function was returning void. */ #define __VA_WRAP_CALL_FUNC __attribute__ ((noinline)) #define VA_WRAP_CALL_COMMON() \ uintptr_t va_wrap_this_bp,va_wrap_old_bp; \ va_wrap_this_bp = va_wrap_get_bp(); \ va_wrap_old_bp = *(uintptr_t *) va_wrap_this_bp; \ va_wrap_this_bp += 2 * sizeof(uintptr_t); \ size_t volatile va_wrap_size = va_wrap_old_bp - va_wrap_this_bp; \ uintptr_t *va_wrap_stack = alloca(va_wrap_size); \ memcpy((void *) va_wrap_stack, \ (void *)(va_wrap_this_bp), va_wrap_size); #if ( __WORDSIZE == 64 ) /* System V AMD64 AB calling convention */ static inline uintptr_t __attribute__((always_inline)) va_wrap_get_bp() { uintptr_t ret; asm volatile ("mov %%rbp, %0":"=r"(ret)); return ret; } #define VA_WRAP_PROLOGUE() \ uintptr_t va_wrap_ret; \ uintptr_t va_wrap_saved_args[7]; \ asm volatile ( \ "mov %%rsi, (%%rax)\n\t" \ "mov %%rdi, 0x8(%%rax)\n\t" \ "mov %%rdx, 0x10(%%rax)\n\t" \ "mov %%rcx, 0x18(%%rax)\n\t" \ "mov %%r8, 0x20(%%rax)\n\t" \ "mov %%r9, 0x28(%%rax)\n\t" \ : \ :"a"(va_wrap_saved_args) \ ); #define VA_WRAP_CALL(func, ret) \ VA_WRAP_CALL_COMMON(); \ va_wrap_saved_args[6] = (uintptr_t)va_wrap_stack; \ asm volatile ( \ "mov (%%rax), %%rsi \n\t" \ "mov 0x8(%%rax), %%rdi \n\t" \ "mov 0x10(%%rax), %%rdx \n\t" \ "mov 0x18(%%rax), %%rcx \n\t" \ "mov 0x20(%%rax), %%r8 \n\t" \ "mov 0x28(%%rax), %%r9 \n\t" \ "mov $0, %%rax \n\t" \ "call *%%rbx \n\t" \ : "=a" (va_wrap_ret) \ : "b" (func), "a" (va_wrap_saved_args) \ : "%rcx", "%rdx", \ "%rsi", "%rdi", "%r8", "%r9", \ "%r10", "%r11", "%r12", "%r14", \ "%r15" \ ); \ ret = (typeof(ret)) va_wrap_ret; #else /* x86 stdcall */ static inline uintptr_t __attribute__((always_inline)) va_wrap_get_bp() { uintptr_t ret; asm volatile ("mov %%ebp, %0":"=a"(ret)); return ret; } #define VA_WRAP_PROLOGUE() \ uintptr_t va_wrap_ret; #define VA_WRAP_CALL(func, ret) \ VA_WRAP_CALL_COMMON(); \ asm volatile ( \ "mov %2, %%esp \n\t" \ "call *%1 \n\t" \ : "=a"(va_wrap_ret) \ : "r" (func), \ "r"(va_wrap_stack) \ : "%ebx", "%ecx", "%edx" \ ); \ ret = (typeof(ret))va_wrap_ret; #endif #endif 

最后你可以像这样打包电话:

 int __VA_WRAP_CALL_FUNC wrap_printf(char *str, ...) { VA_WRAP_PROLOGUE(); int ret; VA_WRAP_CALL(printf, ret); printf("printf returned with %d \n", ret); return ret; } 

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

没有办法转发这样的函数调用,因为您可以检索原始堆栈元素的唯一位置在my_print() 。 像这样封装调用的通常方法是有两个函数,一个将参数转换成各种varargs结构,另一个实际上对这些结构进行操作。 使用这样一个双重函数模型,可以(例如)通过用va_start()初始化my_printf()的结构来包装printf() va_start() ,然后将它们传递给vfprintf()

对不起的话题咆哮,但:

元问题是C中的可变参数接口从一开始就被彻底打破了。 这是缓冲溢出和无效内存访问的邀请,因为如果没有明确的结束信号(没有人真正使用懒惰),参数列表的末尾不能被find。 它总是依赖于深奥的特定于实现的macros,重要的va_copy()macros仅在某些体系结构上受支持。

是的,你可以做到这一点,但它有点难看,你必须知道最多的参数。 此外,如果你在一个架构上的参数不像x86那样通过栈(例如PowerPC),你将不得不知道是否使用了“特殊”types(double,float,altivec等)所以,相应地处理它们。 它可能会很快痛苦,但如果你在x86上,或者如果原始函数有一个明确的和有限的边界,它可以工作。 它仍然是一个黑客 ,将其用于debugging目的。 不要为此build立软件。 无论如何,这是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说,他们被转换为双重,但程序崩溃。 仅此一点就表明,这个解决scheme是一个黑客攻击,并没有一个通用的解决scheme。 在我的例子中,我假定参数的最大数量是8,但是你可以增加这个数字。 包装的函数也只使用整数,但它与其他“常规”参数的工作方式相同,因为它们总是转换为整数。 目标函数将知道它们的types,但你的中间包装不需要。 包装器也不需要知道正确的参数数量,因为目标函数也知道它。 要做有用的工作(除了只logging通话),你可能必须知道两者。

基本上有三种select。

一个是不传递它,而是使用目标函数的可变实现,而不是传递给椭圆。 另一个是使用可变macros。 第三个选项是我失踪的所有东西。

我通常会select一个,因为我觉得这很容易处理。 选项二有一个缺点,因为调用可变参数macros有一些限制。

以下是一些示例代码:

 #include <stdio.h> #include <stdarg.h> #define Option_VariadicMacro(f, ...)\ printf("printing using format: %s", f);\ printf(f, __VA_ARGS__) int Option_ResolveVariadicAndPassOn(const char * f, ... ) { int r; va_list args; printf("printing using format: %s", f); va_start(args, f); r = vprintf(f, args); va_end(args); return r; } void main() { const char * f = "%s %s %s\n"; const char * a = "One"; const char * b = "Two"; const char * c = "Three"; printf("---- Normal Print ----\n"); printf(f, a, b, c); printf("\n"); printf("---- Option_VariadicMacro ----\n"); Option_VariadicMacro(f, a, b, c); printf("\n"); printf("---- Option_ResolveVariadicAndPassOn ----\n"); Option_ResolveVariadicAndPassOn(f, a, b, c); printf("\n"); }