天天看點

C語言可變參數清單解析及簡單應用

在函數原型中,列出函數期望接受的參數,但是原型隻能顯示固定數目的參數。通俗來講就是,當我們給定函數原型時候,我們也就确定了函數的參數的個數,傳遞參數的時候必須按照原型提供的參數個數來傳遞參數。

那麼我們是否可以傳遞參數時候,提供可變個參數呢?

當然是可以的,比如我們常用的printf()函數,我們可以用以下方式傳遞多個參數給此函數。

printf("hello");//一個參數
printf("%d",);//兩個參數
printf("%d%d",,);//三個參數
           

在MSDN幫助文檔中檢視printf()函數的原型可以看到:

C語言可變參數清單解析及簡單應用

可以看到在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 ;
}
           

進行棧幀分析來分析調用原理

C語言可變參數清單解析及簡單應用

繼續閱讀