[C] Instructions for the use of C language variable parameters va_start/va_arg/va_end

###Date: 2018.4.16

===========================================================

Reprinted from: http://blog.sina.com.cn/s/blog_b315f69b0102wi16.html

     In C language programming, I believe that you often use functions such as printf/sprintf/vsprintf. In use, I don't care too much. The "..." variable parameters in the back are now shared with you to share the use instructions of variable parameters. A little opinion is for reference only.
int printf(const char *format,...);
int sprintf( char *string, const char *format, …);
int vsprintf(char *string, constchar *format, ...); 
The function of vsprintf and sprintf is the same, that is, to output the format string to the specified array, the parameters of the sprintf(char *string, char *farmat [,argument,...]) function start from the second parameter and printf is The same, except that sprintf outputs to the specified array, and printf outputs to the screen (a standard output file), so sprintf has more char *string parameters.
The following is an example of using a variable parameter function. When using it, you need to use the va_start/va_arg/va_end macro definitions to handle variable parameters :
      void f_printf(char *fmt,...) //...represents variable parameters (a list of multiple variable parameters, with a special pointer pointing to it), unlimited number and type
     {
          va_list ap;//Initialize pointer to variable parameter list
          //char *ch,ch1;
          //int i;
          char string[256];

          va_start(ap,fmt); //Pay the address of the first variable parameter to ap, that is, ap points to the beginning of the variable parameter list
          //ch =  va_arg( ap, char *); // Take out the value in ap, that is, the first variable parameter, char * is changed according to the actual parameter type passed in, and ap is incremented after calling va_arg ;
           //ch1 =  va_arg( ap, char); // Take out the value in ap, that is, the second variable parameter, char needs to be changed according to the specific type of the variable parameter, and ap is incremented after calling va_arg ;
           //i = va_arg( ap, int ); // Remove the value in ap, that is, the third variable parameter. The int needs to be changed according to the specific type of the variable parameter. After calling va_arg , ap increases automatically;
          vsprintf(string,fmt,ap);//Convert the variable parameters pointed to by the parameters fmt and ap into a formatted string, and put them in the string array .
          va_end(ap);  //ap value is 0, which is of no practical use, mainly for program robustness
          // putchar(ch); //print the fetched character
          printf(string); // print the formatted string
     }
The following are the two macro definitions of va_start/va_arg/va_end (there may be definitions in other OS, there are differences, don't worry about it, it can help understanding)
va_start/va_arg/va_end macro definition (may be different in different OS)
1. The following macro definitions are found in stdarg.h:
typedef int *va_list[1];//va_list variable type definition

#define va_start(ap, parmN)    (void)(*(ap) = __va_start(parmN))
#define va_arg(ap, type) __va_arg(*(ap), type)//Take the contents of variable parameters
#define va_end(ap)                  ((void)(*(ap) = 0))

Instructions:
func( Type para1, Type para2, Type para3, ... )
{
va_list ap;
va_start( ap, para3 ); // The second parameter of va_start must be the parameter before "..."

//At this point ap points to the first variable parameter
//Call va_arg to get the value inside

Type xx = va_arg( ap, Type );

//The Type must be the same here, such as:
//char *p = va_arg( ap, char *);
// int i = va_arg (ap, int);

//If there are multiple parameters to continue to call va_arg, ap will automatically change (usually self-increment)

va_end(ap); 
}
Second, another macro definition:
typedef char * va_list;

#define va_start _crt_va_start
#define va_arg _crt_va_arg
#define va_end _crt_va_end

#define _crt_va_start(ap,v)     ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
#define _crt_va_arg(ap,t)       ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define _crt_va_end(ap)        ( ap = (va_list)0 )

C语言的函数的参数是从右向左压入堆栈的,栈地址从高地址往低地址增长(典型的GCC,此处推荐一个帖子 参看14楼http://bbs.csdn.net/topics/390882650 和一个博客 http://blog.csdn.net/yingxunren/article/details/3979270   仅供参考 ),调用va_start后,按定义的宏运算,ADDRESSOF得到v所在的地址(即第一个固定参数的地址),然后这个地址加上v的大小,则使ap指向第一个可变参数的地址,如图: 

栈底 高地址
| ....... 
| 函数返回地址
| ....... 
| 函数最后一个可变参数
| .... 
| 函数第一个可变参数 //使用va_start后ap指向于此
| 函数最后一个固定参数
| 函数第一个固定参数
栈顶 低地址

然后,用va_arg()取得类型t的可变参数值, 先是让ap指向下一个参数:ap += _INTSIZEOF(t),然后在减去_INTSIZEOF(t),使得表达式结果为ap之前的值,即当前需要得到的参数的地址,强制转换成指向此参数的类型的指针,然后用*取值
     
可变参数用宏定义实现的,具体实现和堆栈相关,因此在不同的场合和不同的OS,应尽量避免使用可变参。

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325991514&siteId=291194637