在函數原型中,列出函數期望接受的參數,但是原型隻能顯示固定數目的參數。通俗來講就是,當我們給定函數原型時候,我們也就确定了函數的參數的個數,傳遞參數的時候必須按照原型提供的參數個數來傳遞參數。
那麼我們是否可以傳遞參數時候,提供可變個參數呢?
當然是可以的,比如我們常用的printf()函數,我們可以用以下方式傳遞多個參數給此函數。
printf("hello");//一個參數
printf("%d",);//兩個參數
printf("%d%d",,);//三個參數
在MSDN幫助文檔中檢視printf()函數的原型可以看到:

可以看到在printf函數參數中有由 “…”組成的參數構成,這是可變函數參數的标準格式,也是标志。我們把參數可變的函數叫做可變參數函數。
可變參數清單是通過宏來實作的,這些宏定義在頭檔案stdarg.h中,這個頭檔案中聲明了一個類型va_lsit和三個宏——va_start、va_arg、va_end。
解析:
va_list類型:這個類型是通過typedef将char 類型進行了重命名,也就是說va_list是char 類型。
va_start宏:用來初始化,它的第一個參數是va_list變量的名字,第二個參數是省略号前最後一個用名字的參數。初始化過程是把va_list類型的變量設定為指向可變參數部分的第一個參數。
va_arg宏:為了通路參數,需要使用這個宏來實作,這個宏接受兩個參數,va_list變量和下一個參數的類型。
va_end宏:使用這個宏來結束通路。
以下是簡單實作用可變參數清單來求不同個數參數的平均值。
/*代碼1*/
#include<stdio.h>
#include<stdlib.h>
#include<stdarg.h>
float average(int num, ...)
{
va_list arg;//聲明va_list類型變量arg
int count;
float sum = ;
va_start(var_arg, num);//初始化變量
for (count = ; count < num; count += )
{
sum += va_arg(arg, int);//通路變量
}
va_end(arg);//調用結束
return sum / num;
}
int main()
{
float aver = ;
aver=average(, , , , , );
printf("%f\n", aver);
system("pause");
return ;
}
那麼可變參數清單是如何實作的呢?原理是什麼呢?我們進入源碼檢視
typedef char * va_list;
#define va_start _crt_va_start;
#define va_arg _crt_va_arg;
#define va_end _crt_va_end;
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define _ADDRESSOF(v) ( &(v) )
#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 )
可以看到va_list類型實質是char *類型。
而va_start、va_arg、va_end經過宏定義為_crt_va_start、_crt_va_arg、_crt_va_end.
為什麼要定義為_crt函數呢?首先我們得了解什麼是CRT函數,一般來說,CRT函數就是标準的C語言函數。而可變參數清單是标準庫的一部分。
為了對宏定義進行分析說明,我們将宏定義全部替換可得到如下:
先來解釋此處的宏定義的作用:
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
_INTSIZEOF(n)整個做的事情就是将n的長度化為int長度的整數倍。
比如n為5,二進制就是101b,int長度為4,二進制為100b,那麼n化為int長度的整數倍就應該為8。
~(sizeof(int) - 1) )就應該為~(4-1)=~(00000011b)=11111100b,這樣任何數& ~(sizeof(int) - 1) )後最後兩位肯定為0,就肯定是4的整數倍了。
(sizeof(n) + sizeof(int) - 1)就是将大于4m但小于等于4(m+1)的數提高到大于等于4(m+1)但小于4(m+2),這樣再& ~(sizeof(int) - 1) )後就正好将原長度補齊到4的倍數了。
(通俗的說,就是當n為1,2,3,4的時候n取值為4. n位5,6,7,8的時候n值取值為8.依次類推)
我們用代碼1進行執行個體分析
float average(int num, ...)
{
char * arg;
int count;
float sum = ;
arg=(char *)(&(num))+;
for (count = ; count < num; count += )
{
sum += (*(int*)((arg+=)-));
}
arg=((char *));
return sum / num;
}
int main()
{
float aver = ;
aver=average(, , , , , );
printf("%f\n", aver);
system("pause");
return ;
}
進行棧幀分析來分析調用原理