347

假设我有一个 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);
 }
4

11 回答 11

222

要传递省略号,您va_list像往常一样初始化 a 并将其传递给您的第二个函数。你不使用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);
}
于 2008-10-15T17:11:06.840 回答
62

在不知道传递给它的参数数量的情况下,没有办法调用(例如)printf,除非您想使用顽皮和不可移植的技巧。

通常使用的解决方案是始终提供 vararg 函数的替代形式,因此printfvprintf取代va_list.... 这些...版本只是版本的包装va_list

于 2008-10-15T17:11:50.473 回答
53

可变参数函数可能很危险。这是一个更安全的技巧:

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

func((type[]){val1,val2,val3,val4,0});
于 2011-11-27T04:51:44.250 回答
29

在宏伟的 C++0x 中,您可以使用可变参数模板:

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...);
}
于 2011-03-24T22:14:54.213 回答
8

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

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);
    }
于 2011-07-26T19:40:49.893 回答
7

你也可以试试宏。

#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;
}
于 2015-04-19T14:23:23.770 回答
7

尽管您可以通过首先将格式化程序存储在本地缓冲区中来解决传递格式化程序的问题,但这需要堆栈并且有时可能是要处理的问题。我尝试了以下,它似乎工作正常。

#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");
}

希望这可以帮助。

于 2018-07-04T07:05:08.420 回答
2

罗斯的解决方案清理了一点。仅当所有参数都是指针时才有效。__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);}
于 2012-10-05T23:49:35.663 回答
1

简短的回答

/// 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

自定义打印功能

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

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;
}
于 2020-11-21T15:32:19.800 回答
0

假设您编写了一个典型的可变参数函数。因为在可变参数之前至少需要一个参数...,所以您必须始终在使用中编写一个额外的参数。

还是你?

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

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

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

于 2015-07-28T11:42:46.043 回答
-5

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

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() 的堆栈地址。所以它只有在两个函数使用相同的堆栈段时才有效。

如果您将任何 var 作为起点, va_start 和 va_arg 宏通常会起作用。因此,如果您愿意,您可以将指针传递给其他函数并使用它们。您可以轻松地制作自己的宏。所有宏所做的只是类型转换内存地址。然而,让它们适用于所有编译器和调用约定是很烦人的。因此,使用编译器附带的通常更容易。

于 2013-05-08T21:30:33.700 回答