【问题标题】:Passing variable number of arguments around传递可变数量的参数
【发布时间】:2010-09-17 08:36:46
【问题描述】:

假设我有一个 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() 是否应该以某种方式返回一个新字符串?
  • 示例没有意义。只是为了显示代码的轮廓。
  • “应该用谷歌搜索”:我不同意。谷歌有很多噪音(不清楚,经常令人困惑的信息)。在 stackoverflow 上有一个好的(投票赞成,接受的答案)真的很有帮助!
  • 只是为了权衡一下:我是从谷歌来的这个问题,因为它是堆栈溢出,所以非常相信答案会很有用。所以问吧!
  • @Ilya:如果没有人在 Google 之外写过东西,那么在 Google 上就没有信息可以搜索了。

标签: c variadic-functions


【解决方案1】:

要传递省略号,您像往常一样初始化 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);
}

【讨论】:

  • 代码取自问题,实际上只是说明如何转换省略号而不是任何功能。如果您查看它,format_string 也几乎没有用处,因为它必须对 fmt 进行原位修改,这当然也不应该这样做。选项将包括完全摆脱 format_string 并使用 vfprintf,但这会假设 format_string 实际做了什么,或者让 format_string 返回不同的字符串。我将编辑答案以显示后者。
  • 如果您的格式字符串碰巧使用与 printf 相同的格式字符串命令,如果您的格式字符串与传递的实际参数不兼容,您还可以使用 gcc 和 clang 等编译器向您发出警告更多详细信息,请参见 GCC 函数属性“格式”:gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html
  • 如果您连续两次传递参数,这似乎不起作用。
  • @fotanus: 如果你用argptr调用一个函数并且被调用的函数使用argptr,唯一安全的做法是调用va_end()然后重启va_start(argptr, fmt);重新初始化。或者,如果您的系统支持,您可以使用va_copy()(C99 和 C11 需要它;C89/90 不需要)。
  • 请注意@ThomasPadron-McCarthy 的评论现在已经过时了,最终的 fprintf 是好的。
【解决方案2】:

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

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

【讨论】:

【解决方案3】:

Variadic Functions 可以是 dangerous。这是一个更安全的技巧:

   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)); })
  • @RichardJ.RossIII 我希望你能扩展你的评论,这样很难阅读,我无法弄清楚代码背后的想法,它实际上看起来非常有趣和有用。
  • @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 巧妙技巧的额外好处……这里有什么问题?
  • 如果你想传递 0 作为参数怎么办?
  • 这要求您的用户记住以 0 结尾的调用。如何更安全?
【解决方案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 中仍然不可用...当然,这可能与您无关!
  • 如果您使用的是 Visual Studio,可以使用 2012 年 11 月的 CTP 将可变参数模板添加到 Visual Studio 2012。如果您使用的是 Visual Studio 2013,您将拥有可变参数模板。
【解决方案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);
    }

【讨论】:

  • 不可移植,依赖编译器,妨碍编译器优化。非常糟糕的解决方案。
  • 至少这实际上回答了问题,没有重新定义问题。
【解决方案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】:

      Ross 的解决方案稍微清理了一下。仅当所有参数都是指针时才有效。如果__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】:

        简答

        /// 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 传递给它,也可以将其与上述方法结合使用。来源here

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

        【讨论】:

          【解决方案10】:

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

          你呢?

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

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

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

          【讨论】:

            【解决方案11】:

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

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2021-10-17
              • 2013-09-21
              • 1970-01-01
              相关资源
              最近更新 更多