传递可变数量的参数

假设我有一个C函数,它有一个可变数量的参数:我如何调用另一个函数,它期望从它内部有一个可变数量的参数,传递进入第一个函数的所有参数?

例子:

void format_string(char *fmt, ...);


void debug_print(int dbg_lvl, char *fmt, ...) {
format_string(fmt, /* how do I pass all the arguments from '...'? */);
fprintf(stdout, fmt);
}
191016 次浏览

为了传递省略号,你像往常一样初始化va_list并简单地将它传递给你的第二个函数。你没有使用va_arg()。特别地;

void format_string(char *fmt,va_list argptr, char *formatted_string);




void debug_print(int dbg_lvl, char *fmt, ...)
{
char formatted_string[MAX_FMT_SIZE];


va_list argptr;
va_start(argptr,fmt);
format_string(fmt, argptr, formatted_string);
va_end(argptr);
fprintf(stdout, "%s",formatted_string);
}

在不知道传递给printf的参数有多少的情况下调用printf是不可能的,除非您想要使用一些淘气且不可移植的技巧。

通常使用的解决方案是始终提供另一种形式的变参数函数,因此printf具有vprintf,它以va_list代替......版本只是va_list版本的包装。

在华丽的c++ 11中,你可以使用可变模板:

template <typename... Ts>
void format_string(char *fmt, Ts ... ts) {}


template <typename... Ts>
void debug_print(int dbg_lvl, char *fmt, Ts... ts)
{
format_string(fmt, ts...);
}

可以对函数调用使用内联汇编。(在这段代码中,我假设参数是字符)。

void format_string(char *fmt, ...);
void debug_print(int dbg_level, int numOfArgs, char *fmt, ...)
{
va_list argumentsToPass;
va_start(argumentsToPass, fmt);
char *list = new char[numOfArgs];
for(int n = 0; n < numOfArgs; n++)
list[n] = va_arg(argumentsToPass, char);
va_end(argumentsToPass);
for(int n = numOfArgs - 1; n >= 0; n--)
{
char next;
next = list[n];
__asm push next;
}
__asm push fmt;
__asm call format_string;
fprintf(stdout, fmt);
}

可变的函数可以是危险< a href = " https://stackoverflow.com/questions/3555583/passing-variable-number-of-arguments-with-different-type-c " > < / >。这里有一个更安全的技巧:

   void func(type* values) {
while(*values) {
x = *values++;
/* do whatever with x */
}
}


func((type[]){val1,val2,val3,val4,0});

罗斯的解决方案清理了一点。只有当所有参数都是指针时才有效。此外,如果__VA_ARGS__为空,语言实现必须支持省略前面的逗号(Visual Studio c++和GCC都支持)。

// pass number of arguments version
#define callVardicMethodSafely(...) {value_t *args[] = {NULL, __VA_ARGS__}; _actualFunction(args+1,sizeof(args) / sizeof(*args) - 1);}




// NULL terminated array version
#define callVardicMethodSafely(...) {value_t *args[] = {NULL, __VA_ARGS__, NULL}; _actualFunction(args+1);}

我不确定这是否适用于所有编译器,但到目前为止对我来说是有效的。

void inner_func(int &i)
{
va_list vars;
va_start(vars, i);
int j = va_arg(vars);
va_end(vars); // Generally useless, but should be included.
}


void func(int i, ...)
{
inner_func(i);
}

你可以添加…到inner_func()如果你想,但你不需要它。它可以工作,因为va_start使用给定变量的地址作为起始点。在这种情况下,我们在func()中给它一个变量的引用。所以它使用这个地址并在堆栈中读取后面的变量。inner_func()函数从func()的堆栈地址中读取。因此,只有当两个函数使用相同的堆栈段时,它才有效。

如果你给va_start和va_arg宏一个变量作为起始点,它们通常会工作。如果你愿意,你也可以把指针传递给其他函数。您可以很容易地创建自己的宏。宏所做的就是对内存地址进行类型转换。然而,让它们适用于所有编译器和调用约定是很烦人的。所以使用编译器自带的通常更容易。

你也可以试试宏。

#define NONE    0x00
#define DBG     0x1F
#define INFO    0x0F
#define ERR     0x07
#define EMR     0x03
#define CRIT    0x01


#define DEBUG_LEVEL ERR


#define WHERESTR "[FILE : %s, FUNC : %s, LINE : %d]: "
#define WHEREARG __FILE__,__func__,__LINE__
#define DEBUG(...)  fprintf(stderr, __VA_ARGS__)
#define DEBUG_PRINT(X, _fmt, ...)  if((DEBUG_LEVEL & X) == X) \
DEBUG(WHERESTR _fmt, WHEREARG,__VA_ARGS__)


int main()
{
int x=10;
DEBUG_PRINT(DBG, "i am x %d\n", x);
return 0;
}

假设你写了一个典型的变进函数。因为在可变参数...之前至少需要一个参数,所以在使用时必须总是编写一个额外的参数。

还是你呢?

如果将可变参数函数包装在宏中,则不需要前面的参数。想想这个例子:

#define LOGI(...)
((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))

这显然要方便得多,因为您不需要每次都指定初始参数。

虽然你可以通过先将它存储在本地缓冲区来解决传递格式化器的问题,但这需要堆栈,有时可能会成为处理问题。我试着跟着做,看起来效果不错。

#include <stdarg.h>
#include <stdio.h>


void print(char const* fmt, ...)
{
va_list arg;
va_start(arg, fmt);
vprintf(fmt, arg);
va_end(arg);
}


void printFormatted(char const* fmt, va_list arg)
{
vprintf(fmt, arg);
}


void showLog(int mdl, char const* type, ...)
{
print("\nMDL: %d, TYPE: %s", mdl, type);


va_list arg;
va_start(arg, type);
char const* fmt = va_arg(arg, char const*);
printFormatted(fmt, arg);
va_end(arg);
}


int main()
{
int x = 3, y = 6;
showLog(1, "INF, ", "Value = %d, %d Looks Good! %s", x, y, "Infact Awesome!!");
showLog(1, "ERR");
}

希望这能有所帮助。

简短的回答

/// logs all messages below this level, level 0 turns off LOG
#ifndef LOG_LEVEL
#define LOG_LEVEL 5  // 0:off, 1:error, 2:warning, 3: info, 4: debug, 5:verbose
#endif
#define _LOG_FORMAT_SHORT(letter, format) "[" #letter "]: " format "\n"


/// short log
#define log_s(level, format, ...)     \
if (level <= LOG_LEVEL)            \
printf(_LOG_FORMAT_SHORT(level, format), ##__VA_ARGS__)


使用

log_s(1, "fatal error occurred");
log_s(3, "x=%d and name=%s",2, "ali");

输出

[1]: fatal error occurred
[3]: x=2 and name=ali

记录文件和行号

const char* _getFileName(const char* path)
{
size_t i = 0;
size_t pos = 0;
char* p = (char*)path;
while (*p) {
i++;
if (*p == '/' || *p == '\\') {
pos = i;
}
p++;
}
return path + pos;
}


#define _LOG_FORMAT(letter, format)      \
"[" #letter "][%s:%u] %s(): " format "\n", _getFileName(__FILE__), __LINE__, __FUNCTION__


#ifndef LOG_LEVEL
#define LOG_LEVEL 5 // 0:off, 1:error, 2:warning, 3: info, 4: debug, 5:verbose
#endif


/// long log
#define log_l(level, format, ...)     \
if (level <= LOG_LEVEL)            \
printf(_LOG_FORMAT(level, format), ##__VA_ARGS__)

使用

log_s(1, "fatal error occurred");
log_s(3, "x=%d and name=%s",2, "ali");

输出

[1][test.cpp:97] main(): fatal error occurred
[3][test.cpp:98] main(): x=2 and name=ali

自定义打印功能

你可以编写自定义打印函数并将...参数传递给它,也可以将其与上面的方法结合使用。来源在这里

int print_custom(const char* format, ...)
{
static char loc_buf[64];
char* temp = loc_buf;
int len;
va_list arg;
va_list copy;
va_start(arg, format);
va_copy(copy, arg);
len = vsnprintf(NULL, 0, format, arg);
va_end(copy);
if (len >= sizeof(loc_buf)) {
temp = (char*)malloc(len + 1);
if (temp == NULL) {
return 0;
}
}
vsnprintf(temp, len + 1, format, arg);
printf(temp); // replace with any print function you want
va_end(arg);
if (len >= sizeof(loc_buf)) {
free(temp);
}
return len;
}