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


评论

您的示例对我来说有点奇怪,因为您将fmt传递给format_string()和fprintf()。 format_string()是否应该以某种方式返回新字符串?

例子没有道理。只是为了显示代码的轮廓。

“应该用谷歌搜索”:我不同意。 Google有很多噪音(不清楚的信息,经常使人困惑)。在stackoverflow上拥有一个良好的(投票同意的答案)确实很有帮助!

只是权衡一下:我是从Google提出这个问题的,并且由于它是堆栈溢出,因此非常有信心答案会很有用。所以请问!

@Ilya:如果没有人在Google以外写下任何东西,那么就没有信息可以在Google上搜索。

#1 楼

要传递省略号,您必须将其转换为va_list并在第二个函数中使用该va_list。具体来说;

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


评论


该代码摘自该问题,实际上只是说明如何转换椭圆而不是任何功能。如果您查看它,format_string也几乎没有用,因为它必须对fmt进行就地修改,当然也不应该这样做。选项可能包括完全放弃format_string并使用vfprintf,但这会假设format_string实际起作用,或者让format_string返回不同的字符串。我将编辑答案以显示后者。

– SmacL
2012年2月24日在16:35

如果您的格式字符串恰好使用了与printf相同的格式字符串命令,那么您也可以使用gcc和clang之类的编译器,以在格式字符串与传入的实际参数不兼容时发出警告。请参见GCC函数属性'格式”以了解更多详细信息:gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html。

–道格·理查森(Doug Richardson)
13年11月16日19:00



如果您连续两次传递args,这似乎不起作用。

– fotanus
2015年6月10日4:20



@fotanus:如果您使用argptr调用一个函数,而被调用的函数完全使用argptr,则唯一安全的方法是调用va_end()然后重新启动va_start(argptr,fmt);重新初始化。或者,如果您的系统支持va_copy(),则可以使用va_copy()(C99和C11要求;而C89 / 90不需要)。

–乔纳森·莱弗勒(Jonathan Leffler)
15年6月15日在1:32

请注意,@ ThomasPadron-McCarthy的评论现在已经过时,最终的fprintf还可以。

–弗雷德里克
16-09-13在20:19

#2 楼

除非您想了解顽皮和不可移植的技巧,否则就无法在不知道要传递给它的参数的情况下调用printf。 vararg函数的一种替代形式,因此printf具有vprintf,它将va_list替换为......版本只是va_list版本的包装。

评论


请注意,vsyslog不符合POSIX。

–patryk.beza
16年8月9日在12:29

#3 楼

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

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

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


评论


更好的方法是:#define callVardicMethodSafely(values ...)({values * v = {values}; _actualFunction(values,sizeof(v)/ sizeof(* v));})

–理查德·罗斯三世(Richard J. Ross III)
2012年3月3日在22:54

@ RichardJ.RossIII我希望您能扩展您的评论,它很难像这样阅读,我无法理解代码背后的想法,它实际上看起来非常有趣和有用。

–佩内洛普
2012年3月30日上午10:09

@ArtOfWarfare我不确定我是否同意它是一个糟糕的hack,Rose有很好的解决方案,但是它涉及到输入func((type []){val1,val2,0});如果您有#define func_short_cut(...)func((type []){VA_ARGS}),这会很笨拙。那么您可以简单地调用func_short_cut(1,2,3,4,0);这为您提供了与普通可变参数函数相同的语法,并具有Rose巧妙技巧的额外好处...这是什么问题?

–chrispepper1989
2013年7月24日14:05



如果要传递0作为参数怎么办?

–朱利安·金(Julian Gold)
2014年9月10日上午8:50

这要求您的用户记住以0结束的呼叫。这如何更安全?

–cp.engr
16-2-26在21:26

#4 楼

在宏伟的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...);
}


评论


不要忘记,可变参数模板在Visual Studio中仍然不可用...当然,这可能对您完全没有关系!

–汤姆·斯威
2013年9月7日上午1:01

如果您使用的是Visual Studio,则可以使用2012年11月CTP将可变参数模板添加到Visual Studio 2012。如果您使用的是Visual Studio 2013,则将具有可变参数模板。

–user2023370
2013年9月7日下午14:12

#5 楼

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

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-10-20 18:55

至少这实际上回答了问题,而没有重新定义问题。

– kungfooman
16-11-25在5:43

#6 楼

您也可以尝试使用宏。

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


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


希望有帮助。

#8 楼

罗斯的解决方案有所清除。仅当所有args是指针时才有效。同样,如果__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);}


#9 楼

假设您已经编写了一个典型的可变参数函数。因为可变参数...之前至少需要一个参数,所以您必须始终在用法中编写一个额外的参数。在宏中,不需要前面的arg。考虑以下示例:

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


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

#10 楼

简短答案
 /// 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;
}
 


#11 楼

我不确定这是否适用于所有编译器,但到目前为止对我来说仍然有效。想要,但您不需要。之所以有效,是因为va_start使用给定变量的地址作为起点。在这种情况下,我们给它一个对func()中变量的引用。因此它使用该地址并在堆栈上读取该地址之后的变量。 inner_func()函数正在读取func()的堆栈地址。因此,只有两个函数都使用相同的堆栈段时,它才有效。因此,如果您愿意,可以将指针传递给其他函数,也可以使用它们。您可以轻松地制作自己的宏。所有宏所做的只是类型转换内存地址。但是使它们适用于所有编译器和调用约定很烦人。因此,使用编译器随附的程序通常会更容易。