首先我们先看看它的头文件是怎么描述的
stdarg.h
#pragma once
#ifndef _INC_STDARG
#define _INC_STDARG
#if !defined(_WIN32)
#error ERROR: Only Win32 target supported!
#endif
#include <vadefs.h>
#define va_start _crt_va_start
#define va_arg _crt_va_arg
#define va_end _crt_va_end
#endif /* _INC_STDARG */
#define _crt_va_arg(ap,t) (*(t *)((ap += _SLOTSIZEOF(t)+ _APALIGN(t,ap)) \
-_SLOTSIZEOF(t)))
#include<vadefs.h>
#define _crt_va_arg(ap,t)(*(t *)((ap += _SLOTSIZEOF(t)+_APALIGN(t,ap)) \-_SLOTSIZEOF(t)))
#define _crt_va_end(ap) ( ap = (va_list)0 )
.va_list用于声明一个变量,我们知道函数的可变参数列表其实就是一个字符串,所以va_list才被声明为字符型指针,这个类型用于声明一个指向参数列表的字符型指针变量
va_start(ap,v),它的第一个参数是指向可变参数字符串的变量,第二个参数是可变参数函数的第一个参数,通常用于指定可变参数列表中参数的个数。
va_arg(ap,t),它的第一个参数指向可变参数字符串的变量,第二个参数是可变参数的类型。
va_end(ap) 用于将存放可变参数字符串的变量清空(赋值为NULL).
接下来看一个例子
static struct ExprNode * MakeExprNode(enum Token_Type opcode, ...) {
struct ExprNode *ExprPtr = new (struct ExprNode);
va_list ArgPtr;
ExprPtr->OpCode = opcode; // 接受记号的类别
va_start(ArgPtr, opcode);
switch(opcode) { // 根据记号的类别构造不同的节点
case CONST_ID: // 常数
ExprPtr->Content.CaseConst = (double)va_arg(ArgPtr, double);
break;
case T:
ExprPtr->Content.CaseParmPtr = &Parameter;
break;
case FUNC:
ExprPtr->Content.CaseFunc.MathFuncPtr = (FuncPtr)va_arg(ArgPtr, FuncPtr);
ExprPtr->Content.CaseFunc.Child = (struct ExprNode *) va_arg(ArgPtr, struct ExprNode *);
break;
default:
ExprPtr->Content.CaseOperator.Left = (struct ExprNode *)va_arg(ArgPtr, struct ExprNode *);
ExprPtr->Content.CaseOperator.Right = (struct ExprNode *)va_arg(ArgPtr, struct ExprNode *);
break;
}
va_end(ArgPtr);
return ExprPtr;
}
上述代码是语法分析的生成语法树的一个实现代码;通过这段代码我们能看清楚的是
va_start的功能是要把ap指针指向可变参数的第一个参数位置去
#define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
. va_arg是要从ap中取下一个参数,即第一个参数。以此类推。
#define _crt_va_arg(ap,t) ( (t )((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
注意加粗的ap 实际上是等于 ap = ap + ……;
. va_end(ap) 将声明的ap指针置为空,因为指针使用后最后设置为空