可变参数问题

时间:2022-05-15 17:05:20
写一个可变参数的函数
void func(char str, ...)
{

}

大家说说函数里面如何知道调用时候传进去几个参数?
是不是只能根据前面的固定参数传进去些标志,才能知道传进去几个可变参数?
例如:printf() 函数这样。根据printf("%s %d",)里的%s, %d知道有几个参数?

有没有别的方法获得调用时候传了几个可变参数?

18 个解决方案

#1


不管几个参数都是字符串,自己数

#2


msdn中搜~

va_list
va_start
va_end

#3


通过几个宏来确定的,转载:
===========================
一、什么是可变参数
我们在C语言编程中有时会遇到一些参数个数可变的函数,例如printf()函数,其函数原型为:
int printf( const char* format, ...);
它除了有一个参数format固定以外,后面跟的参数的个数和类型是可变的(用三个点“…”做参数占位符),实际调用时可以有以下的形式: printf("%d",i);
printf("%s",s);
printf("the number is %d ,string is:%s", i, s);   
以上这些东西已为大家所熟悉。但是究竟如何写可变参数的C函数以及这些可变参数的函数编译器是如何实现,这个问题却一直困扰了我好久。本文希望能对大家有些帮助.
long sum(int i,...)
{
  int *p,j;
  long s = 0;
  p = &i+1;
  for (j=0;j<i;j++)
    s += p[j];
   return s;
}
long Sum = sum(3,1,2,3);
printf("%ld",Sum);
Sum == 6

二、写一个简单的可变参数的C函数
先看例子程序。该函数至少有一个整数参数,其后占位符…,表示后面参数的个数不定. 在这个例子里,所有的输入参数必须都是整数,函数的功能只是打印所有参数的值.
函数代码如下:
//示例代码1:可变参数函数的使用
#include "stdio.h"
#include "stdarg.h"
void simple_va_fun(int start, ...)
{
    va_list arg_ptr;
    int nArgValue =start;
    int nArgCout=0;     //可变参数的数目
    va_start(arg_ptr,start); //以固定参数的地址为起点确定变参的内存起始地址。
    do
    {
        ++nArgCout;
        printf("the %d th arg: %d",nArgCout,nArgValue);     //输出各参数的值
        nArgValue = va_arg(arg_ptr,int);                      //得到下一个可变参数的值
    } while(nArgValue != -1);               
    return;
}
int main(int argc, char* argv[])
{
    simple_va_fun(100,-1);
    simple_va_fun(100,200,-1);
       return 0;
}
下面解释一下这些代码
从这个函数的实现可以看到,我们使用可变参数应该有以下步骤:
⑴由于在程序中将用到以下这些宏:
void va_start( va_list arg_ptr, prev_param );
type va_arg( va_list arg_ptr, type );
void va_end( va_list arg_ptr );
va在这里是variable-argument(可变参数)的意思.
这些宏定义在stdarg.h中,所以用到可变参数的程序应该包含这个头文件.
⑵函数里首先定义一个va_list型的变量,这里是arg_ptr,这个变
量是存储参数地址的指针.因为得到参数的地址之后,再结合参数的类型,才能得到参数的值。
⑶然后用va_start宏初始化⑵中定义的变量arg_ptr,这个宏的第二个参数是可变参数列表的前一个参数,即最后一个固定参数.
⑷然后依次用va_arg宏使arg_ptr返回可变参数的地址,得到这个地址之后,结合参数的类型,就可以得到参数的值。
⑸设定结束条件,这里的条件就是判断参数值是否为-1。注意被调的函数在调用时是不知道可变参数的正确数目的,程序员必须自己在代码中指明结束条件。至于为什么它不会知道参数的数目,读者在看完这几个宏的内部实现机制后,自然就会明白。



(二)可变参数在编译器中的处理
我们知道va_start,va_arg,va_end是在stdarg.h中被定义成宏的,  由于1)硬件平台的不同 2)编译器的不同,所以定义的宏也有所不同,下面看一下VC++6.0中stdarg.h里的代码(文件的路径为VC安装目录下的\vc98\ include\stdarg.h)
typedef char *  va_list;
#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap)      ( ap = (va_list)0 )
下面我们解释这些代码的含义:
1、首先把va_list被定义成char*,这是因为在我们目前所用的PC机上,字符指针类型可以用来存储内存单元地址。而在有的机器上va_list是被定义成void*的
2、定义_INTSIZEOF(n)主要是为了某些需要内存的对齐的系统.这个宏的目的是为了得到最后一个固定参数的实际内存大?gt;>T谖业幕魃现苯佑胹izeof运朔创妫猿绦虻脑诵薪峁挂裁挥杏跋臁#ê笪慕吹轿易约旱氖迪郑?
3、 va_start的定义为 &v+_INTSIZEOF(v) ,这里&v是最后一个固定参数的起始地址,再加上其实际占用大小后,就得到了第一个可变参数的起始内存地址。所以我们运行va_start (ap, v)以后,ap指向第一个可变参数在的内存地址,有了这个地址,以后的事情就简单了。
这里要知道两个事情:
    ⑴在intel+windows的机器上,函数栈的方向是向下的,栈顶指针的内存地址低于栈底指针,所以先进栈的数据是存放在内存的高地址处。
    (2)在VC等绝大多数C编译器中,默认情况下,参数进栈的顺序是由右向左的,因此,参数进栈以后的内存模型如下图所示:最后一个固定参数的地址位于第一个可变参数之下,并且是连续存储的。
|——————————————————————————|
|  最后一个可变参数             |   ->高内存地址处
|——————————————————————————|
   ...................
|——————————————————————————|
|  第N个可变参数               |     ->va_arg(arg_ptr,int)后arg_ptr所指的地方,
|                               |     即第N个可变参数的地址。
|——————————————— |     
   ………………………….
|——————————————————————————|
|  第一个可变参数                |     ->va_start(arg_ptr,start)后arg_ptr所指的地方
|                               |     即第一个可变参数的地址
|——————————————— |     
|———————————————————————— ——|
|                               |
|  最后一个固定参数             |    -> start的起始地址
|—————————————— —|       .................
|—————————————————————————— |
|                               |  
|——————————————— |  -> 低内存地址处

(4) va_arg():有了va_start的良好基础,我们取得了第一个可变参数的地址,在va_arg()里的任务就是根据指定的参数类型取得本参数的值,并且把指针调到弦桓霾问钠鹗嫉刂贰?
因此,现在再来看va_arg()的实现就应该心中有数了:
#define va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
这个宏做了两个事情,
       ①用用户输入的类型名对参数地址进行强制类型转换,得到用户所需要的值
   ②计算出本参数的实际大小,将指针调到本参数的结尾,也就是下一个参数的首地址,以便后续处理。
(5) va_end宏的解释:x86平台定义为ap=(char*)0;使ap不再指向堆栈,而是跟NULL一样.有些直接定义为((void*)0),这样编译器不会为va_end产生代码,例如gcc在linux的x86平台就是这样定义的. 在这里大家要注意一个问题:由于参数的地址用于 va_start宏,所以参数不能声明为寄存器变量或作为函数或数组类型. 关于va_start, va_arg, va_end的描述就是这些了,我们要注意的 是不同的操作系统和硬件平台的定义有些不同,但原理却是相似的.

#4


键键,跑得真快:)

#5


呵呵~往往偶跑得快的帖子却不结帐-_-!!

#6


是不是只能根据前面的固定参数传进去些标志,才能知道传进去几个可变参数?

#7


固定参数当做基址用

#8


大家说说函数里面如何知道调用时候传进去几个参数?



比如你先在main前面来了一句
int func(int,...)
系统在预编译的时候就知道参数类型啦

#9


或者将函数写在入口地址前面也可以的

只是写在后面一定要在前面声明的

编译器不然不知道

#10


printf()函数就是根据前面的format格式才知道处理几个可变参数的吧?

例如:printf("%s %d", a, b);

如果这样写 printf("%s%d%s%d", a); 前面的format里有4个%.. ,但后面只传了一个可变参数a. 这样调用会出现 ,core dump 错误。

所以我觉得没办法知道你调用的时候传了几个参数进去。

#11


上面说的
long sum(int i,...)
{
  int *p,j;
  long s = 0;
  p = &i+1;
  for (j=0;j<i;j++)
    s += p[j];
   return s;
}
long Sum = sum(3,1,2,3);
printf("%ld",Sum);
Sum == 6

这个调用的时候不是指定了有几个参数吗sum(3,1,2,3) .3不是说几个参数吗?

如果这样调用sum(4, 1, 2, 3). 就会出错误吧。 程序里面执行了4次 va_arg();

#12


如果这样调用sum(4, 1, 2, 3). 就会出错误吧。 程序里面执行了4次 va_arg();
------------------------------------
的确!

#13


所以说我就是问:如何能得到调用时候传了几个参数,这样就能指定执行几次va_arg()了啊

#14


只能通过第一个参数

#15


没有别的更好的方法吗?
大家有更好的方法分享下哈。。

#16


貌似没有知道的

#17


"..."本质上是一个void *
了解了这点你就不会异想天开了

#18


了解了,谢谢

#1


不管几个参数都是字符串,自己数

#2


msdn中搜~

va_list
va_start
va_end

#3


通过几个宏来确定的,转载:
===========================
一、什么是可变参数
我们在C语言编程中有时会遇到一些参数个数可变的函数,例如printf()函数,其函数原型为:
int printf( const char* format, ...);
它除了有一个参数format固定以外,后面跟的参数的个数和类型是可变的(用三个点“…”做参数占位符),实际调用时可以有以下的形式: printf("%d",i);
printf("%s",s);
printf("the number is %d ,string is:%s", i, s);   
以上这些东西已为大家所熟悉。但是究竟如何写可变参数的C函数以及这些可变参数的函数编译器是如何实现,这个问题却一直困扰了我好久。本文希望能对大家有些帮助.
long sum(int i,...)
{
  int *p,j;
  long s = 0;
  p = &i+1;
  for (j=0;j<i;j++)
    s += p[j];
   return s;
}
long Sum = sum(3,1,2,3);
printf("%ld",Sum);
Sum == 6

二、写一个简单的可变参数的C函数
先看例子程序。该函数至少有一个整数参数,其后占位符…,表示后面参数的个数不定. 在这个例子里,所有的输入参数必须都是整数,函数的功能只是打印所有参数的值.
函数代码如下:
//示例代码1:可变参数函数的使用
#include "stdio.h"
#include "stdarg.h"
void simple_va_fun(int start, ...)
{
    va_list arg_ptr;
    int nArgValue =start;
    int nArgCout=0;     //可变参数的数目
    va_start(arg_ptr,start); //以固定参数的地址为起点确定变参的内存起始地址。
    do
    {
        ++nArgCout;
        printf("the %d th arg: %d",nArgCout,nArgValue);     //输出各参数的值
        nArgValue = va_arg(arg_ptr,int);                      //得到下一个可变参数的值
    } while(nArgValue != -1);               
    return;
}
int main(int argc, char* argv[])
{
    simple_va_fun(100,-1);
    simple_va_fun(100,200,-1);
       return 0;
}
下面解释一下这些代码
从这个函数的实现可以看到,我们使用可变参数应该有以下步骤:
⑴由于在程序中将用到以下这些宏:
void va_start( va_list arg_ptr, prev_param );
type va_arg( va_list arg_ptr, type );
void va_end( va_list arg_ptr );
va在这里是variable-argument(可变参数)的意思.
这些宏定义在stdarg.h中,所以用到可变参数的程序应该包含这个头文件.
⑵函数里首先定义一个va_list型的变量,这里是arg_ptr,这个变
量是存储参数地址的指针.因为得到参数的地址之后,再结合参数的类型,才能得到参数的值。
⑶然后用va_start宏初始化⑵中定义的变量arg_ptr,这个宏的第二个参数是可变参数列表的前一个参数,即最后一个固定参数.
⑷然后依次用va_arg宏使arg_ptr返回可变参数的地址,得到这个地址之后,结合参数的类型,就可以得到参数的值。
⑸设定结束条件,这里的条件就是判断参数值是否为-1。注意被调的函数在调用时是不知道可变参数的正确数目的,程序员必须自己在代码中指明结束条件。至于为什么它不会知道参数的数目,读者在看完这几个宏的内部实现机制后,自然就会明白。



(二)可变参数在编译器中的处理
我们知道va_start,va_arg,va_end是在stdarg.h中被定义成宏的,  由于1)硬件平台的不同 2)编译器的不同,所以定义的宏也有所不同,下面看一下VC++6.0中stdarg.h里的代码(文件的路径为VC安装目录下的\vc98\ include\stdarg.h)
typedef char *  va_list;
#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap)      ( ap = (va_list)0 )
下面我们解释这些代码的含义:
1、首先把va_list被定义成char*,这是因为在我们目前所用的PC机上,字符指针类型可以用来存储内存单元地址。而在有的机器上va_list是被定义成void*的
2、定义_INTSIZEOF(n)主要是为了某些需要内存的对齐的系统.这个宏的目的是为了得到最后一个固定参数的实际内存大?gt;>T谖业幕魃现苯佑胹izeof运朔创妫猿绦虻脑诵薪峁挂裁挥杏跋臁#ê笪慕吹轿易约旱氖迪郑?
3、 va_start的定义为 &v+_INTSIZEOF(v) ,这里&v是最后一个固定参数的起始地址,再加上其实际占用大小后,就得到了第一个可变参数的起始内存地址。所以我们运行va_start (ap, v)以后,ap指向第一个可变参数在的内存地址,有了这个地址,以后的事情就简单了。
这里要知道两个事情:
    ⑴在intel+windows的机器上,函数栈的方向是向下的,栈顶指针的内存地址低于栈底指针,所以先进栈的数据是存放在内存的高地址处。
    (2)在VC等绝大多数C编译器中,默认情况下,参数进栈的顺序是由右向左的,因此,参数进栈以后的内存模型如下图所示:最后一个固定参数的地址位于第一个可变参数之下,并且是连续存储的。
|——————————————————————————|
|  最后一个可变参数             |   ->高内存地址处
|——————————————————————————|
   ...................
|——————————————————————————|
|  第N个可变参数               |     ->va_arg(arg_ptr,int)后arg_ptr所指的地方,
|                               |     即第N个可变参数的地址。
|——————————————— |     
   ………………………….
|——————————————————————————|
|  第一个可变参数                |     ->va_start(arg_ptr,start)后arg_ptr所指的地方
|                               |     即第一个可变参数的地址
|——————————————— |     
|———————————————————————— ——|
|                               |
|  最后一个固定参数             |    -> start的起始地址
|—————————————— —|       .................
|—————————————————————————— |
|                               |  
|——————————————— |  -> 低内存地址处

(4) va_arg():有了va_start的良好基础,我们取得了第一个可变参数的地址,在va_arg()里的任务就是根据指定的参数类型取得本参数的值,并且把指针调到弦桓霾问钠鹗嫉刂贰?
因此,现在再来看va_arg()的实现就应该心中有数了:
#define va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
这个宏做了两个事情,
       ①用用户输入的类型名对参数地址进行强制类型转换,得到用户所需要的值
   ②计算出本参数的实际大小,将指针调到本参数的结尾,也就是下一个参数的首地址,以便后续处理。
(5) va_end宏的解释:x86平台定义为ap=(char*)0;使ap不再指向堆栈,而是跟NULL一样.有些直接定义为((void*)0),这样编译器不会为va_end产生代码,例如gcc在linux的x86平台就是这样定义的. 在这里大家要注意一个问题:由于参数的地址用于 va_start宏,所以参数不能声明为寄存器变量或作为函数或数组类型. 关于va_start, va_arg, va_end的描述就是这些了,我们要注意的 是不同的操作系统和硬件平台的定义有些不同,但原理却是相似的.

#4


键键,跑得真快:)

#5


呵呵~往往偶跑得快的帖子却不结帐-_-!!

#6


是不是只能根据前面的固定参数传进去些标志,才能知道传进去几个可变参数?

#7


固定参数当做基址用

#8


大家说说函数里面如何知道调用时候传进去几个参数?



比如你先在main前面来了一句
int func(int,...)
系统在预编译的时候就知道参数类型啦

#9


或者将函数写在入口地址前面也可以的

只是写在后面一定要在前面声明的

编译器不然不知道

#10


printf()函数就是根据前面的format格式才知道处理几个可变参数的吧?

例如:printf("%s %d", a, b);

如果这样写 printf("%s%d%s%d", a); 前面的format里有4个%.. ,但后面只传了一个可变参数a. 这样调用会出现 ,core dump 错误。

所以我觉得没办法知道你调用的时候传了几个参数进去。

#11


上面说的
long sum(int i,...)
{
  int *p,j;
  long s = 0;
  p = &i+1;
  for (j=0;j<i;j++)
    s += p[j];
   return s;
}
long Sum = sum(3,1,2,3);
printf("%ld",Sum);
Sum == 6

这个调用的时候不是指定了有几个参数吗sum(3,1,2,3) .3不是说几个参数吗?

如果这样调用sum(4, 1, 2, 3). 就会出错误吧。 程序里面执行了4次 va_arg();

#12


如果这样调用sum(4, 1, 2, 3). 就会出错误吧。 程序里面执行了4次 va_arg();
------------------------------------
的确!

#13


所以说我就是问:如何能得到调用时候传了几个参数,这样就能指定执行几次va_arg()了啊

#14


只能通过第一个参数

#15


没有别的更好的方法吗?
大家有更好的方法分享下哈。。

#16


貌似没有知道的

#17


"..."本质上是一个void *
了解了这点你就不会异想天开了

#18


了解了,谢谢