在C中,是否可以转发可变参数函数的调用?如,
int my_printf(char *fmt, ...) {
fprintf(stderr, "Calling printf with fmt %s", fmt);
return SOMEHOW_INVOKE_LIBC_PRINTF;
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,以上述方式转发调用显然不是必需的(因为你可以用其他方式记录调用,或者使用vfprintf),但是我正在处理的代码库要求包装器做一些实际的工作,并且没有没有(并且不能添加)类似于vfprintf的辅助函数.
[更新:基于迄今为止提供的答案,似乎存在一些混淆.用另一种方式表达问题:通常,你可以包装一些任意的可变参数函数而不修改该函数的定义.
Ada*_*eld 149
如果你不具备的功能类似vfprintf,需要一个va_list替代的参数个数可变,你不能做到这一点.看到http://c-faq.com/varargs/handoff.html.
例:
void myfun(const char *fmt, va_list argp) {
vfprintf(stderr, fmt, argp);
}
Run Code Online (Sandbox Code Playgroud)
CB *_*ley 57
不是直接的,但是变量函数与varargs样式替代函数成对出现是常见的(并且在标准库中几乎普遍存在这种情况).例如printf/vprintf
v ...函数采用va_list参数,其实现通常使用编译器特定的"宏魔法"来完成,但是你可以保证从这样的可变参数函数调用v ...样式函数将起作用:
#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;
}
Run Code Online (Sandbox Code Playgroud)
这应该会给你你想要的效果.
如果您正在考虑编写可变参数库函数,您还应该考虑将va_list样式伴随作为库的一部分提供.从您的问题中可以看出,它可以证明对您的用户有用.
Com*_*ger 46
C99支持具有可变参数的宏 ; 根据您的编译器,您可能能够声明一个执行您想要的宏:
#define my_printf(format, ...) \
do { \
fprintf(stderr, "Calling printf with fmt %s\n", format); \
some_other_variadac_function(format, ##__VA_ARGS__); \
} while(0)
Run Code Online (Sandbox Code Playgroud)
但是,一般情况下,最好的解决方案是使用您尝试包装的函数的va_list形式(如果存在).
Gre*_*ill 11
几乎使用以下设施<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;
}
Run Code Online (Sandbox Code Playgroud)
请注意,您需要使用vprintf版本而不是普通版本printf.在没有使用的情况下,没有办法在这种情况下直接调用可变参数函数va_list.
col*_*tox 10
由于不可能以一种很好的方式转发这样的调用,我们通过设置一个带有原始堆栈帧副本的新堆栈帧来解决这个问题.然而,这是非常不可移植的并且做出各种假设,例如代码使用帧指针和"标准"调用约定.
此头文件允许包装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
Run Code Online (Sandbox Code Playgroud)
最后你可以像这样包装调用:
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;
}
Run Code Online (Sandbox Code Playgroud)
gcc 提供了一个可以做到这一点的扩展:__builtin_apply和亲戚。请参阅gcc 手册中的构造函数调用。
一个例子:
#include <stdio.h>
int my_printf(const char *fmt, ...) {
void *args = __builtin_apply_args();
printf("Hello there! Format string is %s\n", fmt);
void *ret = __builtin_apply((void (*)())printf, args, 1000);
__builtin_return(ret);
}
int main(void) {
my_printf("%d %f %s\n", -37, 3.1415, "spam");
return 0;
}
Run Code Online (Sandbox Code Playgroud)
文档中有一些警告,它在更复杂的情况下可能不起作用。并且您必须对参数的最大大小进行硬编码(这里我使用了 1000)。但对于涉及用 C 语言或汇编语言剖析堆栈的其他方法,它可能是一种合理的替代方法。