c实现:

基本用法

c标准库提供一套可变参数列表使用方法,va_list / va_start / va_arg / va_end 。

demo:

double average ( int num, ... )
{
    va_list arguments;                     
    double sum = 0;

    /* Initializing arguments to store all values after num */
    va_start ( arguments, num );           
    /* Sum all the inputs; we still rely on the function caller to tell us how
     * many there are */
    for ( int x = 0; x < num; x++ )        
    {
        sum += va_arg ( arguments, double ); 
    }
    va_end ( arguments );                  // Cleans up the list

    return sum / num;                      
}

va_start 接受一个 va_list 和 函数的入参的最后一个参数,va_start 返回的时候,就已经把入参的 ... 中的数据代理给 va_list了,其实是 va_list 是一个指针,指向了 ... 中的第一个参数。

va_arg 会使用指定的数据类型解析 va_list 中最前面的那个参数并返回,底层上是找到 va_list 指向的地址,然后用入参中的数据类型去解析这个地址里面的值并返回。这里第二个入参的数据类型很重要,这决定了如何解析 va_list 指向的地址内的值。比如如果是char,则va_list只读一个字节,然后va_list会向高地址移动一个字节以备下一次va_arg读取操作。如果是64位上的long,则一次读取8个字节。

va_end 用来释放va_list,底层上入参出栈操作,这里不用关心,只需要记住函数退出之前使用va_end 释放 va_list即可。

嵌套

某些函数接收的是 ... 作为入参,而某些函数接收的是 va_list 作为入参,接收 ... 作为入参的函数是无法被 wrapper 的,而 va_list 作为入参则可以通过传 va_list 实现wrapper。比如:

无法被wrapper的 g_object_set

void
g_object_set (gpointer     _object,
	      const gchar *first_property_name,
	      ...)
{
  GObject *object = _object;
  va_list var_args;
  
  g_return_if_fail (G_IS_OBJECT (object));
  
  va_start (var_args, first_property_name);
  g_object_set_valist (object, first_property_name, var_args);
  va_end (var_args);
}

可以被wrapper 的 g_object_set_valist

void
g_object_set_valist (GObject	 *object,
		     const gchar *first_property_name,
		     va_list	  var_args)
{
  GObjectNotifyQueue *nqueue;
  const gchar *name;
  
  g_return_if_fail (G_IS_OBJECT (object));
  
  g_object_ref (object);
  nqueue = g_object_notify_queue_freeze (object, FALSE);
  
  name = first_property_name;
  while (name)
    {
      GValue value = G_VALUE_INIT;
      GParamSpec *pspec;
      gchar *error = NULL;
      
      pspec = g_param_spec_pool_lookup (pspec_pool,
					name,
					G_OBJECT_TYPE (object),
					TRUE);

      if (!g_object_set_is_valid_property (object, pspec, name))
        break;

      G_VALUE_COLLECT_INIT (&value, pspec->value_type, var_args,
			    0, &error);
      if (error)
	{
	  g_warning ("%s: %s", G_STRFUNC, error);
	  g_free (error);
          g_value_unset (&value);
	  break;
	}

      consider_issuing_property_deprecation_warning (pspec);
      object_set_property (object, pspec, &value, nqueue);
      g_value_unset (&value);

      name = va_arg (var_args, gchar*);
    }

  g_object_notify_queue_thaw (object, nqueue);
  g_object_unref (object);
}

再举个例子

printf的函数原型为:int printf(const char* fmt,...) 

vprintf的函数原型为:int vprintf(char* fmt, va_list arg)

如果对printf做wrapper则不行,因为printf不接受va_list入参:

int myprintf(const char* fmt, ...)
{
    va_list li;
    va_start(li,fmt);
    printf(fmt,li);
    va_end(li);
}

如果对vprintf做wrapper则可以:

int myprintf(const char* fmt, ...)
{
    va_list li;
    va_start(li,fmt);
    vprintf(fmt,li);
    va_end(li);
}

c++实现:

c++有模板参数列表类来实现动态参数列表功能,不过限制是参数列表只能是相同类型。