传递可变数量的参数


333

假设我有一个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
您的示例对我来说有点奇怪,因为您将fmt传递给format_string()和fprintf()。format_string()是否应该以某种方式返回新字符串?
克里斯托弗·约翰逊

2
例子没有道理。只是为了显示代码的轮廓。
森特·马蒂

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

71
只是权衡一下:我是从Google提出这个问题的,因为它是堆栈溢出的,所以非常有信心答案会很有用。所以问走吧!
tenpn

32
@Ilya:如果没有人在Google以外写下任何东西,那么就没有信息可以在Google上搜索。
埃里克·卡普伦

Answers:


211

要传递省略号,您必须将其转换为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);
}

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

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

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

2
@fotanus:如果您使用调用函数,argptr而被调用函数完全使用argptr,则唯一安全的事情是调用va_end(),然后重新启动va_start(argptr, fmt);以重新初始化。或者va_copy()如果您的系统支持它,则可以使用(C99和C11要求; C89 / 90不要求)。
乔纳森·莱夫勒

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

59

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

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


请注意,vsyslog 符合POSIX
patryk.beza,2016年

53

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

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

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

11
更好的办法是: #define callVardicMethodSafely(values...) ({ values *v = { values }; _actualFunction(values, sizeof(v) / sizeof(*v)); })
理查德·罗斯三世

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

5
@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

9
如果要传递0作为参数怎么办?
朱利安·金

1
这要求您的用户记住以0结束的呼叫。这如何更安全?
cp.engr '16

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

不要忘记,可变参数模板在Visual Studio中仍然不可用...当然,您可能完全不用担心!
汤姆·史威利

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

7

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

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

4
不可移植,取决于编译器,并阻止编译器优化。非常糟糕的解决方案。
Geoffroy

4
也没有删除的新内容。
user7116

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

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

6

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

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

希望这可以帮助。


2

罗斯的解决方案有所清除。仅当所有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);}

0

假设您已经编写了一个典型的可变参数函数。由于可变参数之前至少需要一个参数...,因此必须始终在用法中编写一个额外的参数。

还是你

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

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

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


-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()的堆栈地址。因此,仅当两个函数使用相同的堆栈段时,它才有效。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.