自己實現一個簡單可變引數函式

不会笑的孩子發表於2024-11-03

什麼是可變引數

在C語言程式設計中有時會遇到一些引數可變的函式、例如printf()、scanf(),其函式原型為:

int printf(const char *format,...)
int scanf(const char *format,...)

它除了有一個引數format固定以外,後面的引數其個數和型別都是可變的,用三個點"..."作為引數佔位符號。

引數列表的構成

任何一個可變引數都可以分為兩個部分:固定引數和可選引數。至少有一個固定引數,其宣告與普通函式宣告相同;可選引數由於數目不定(0個或以上),宣告時必須用"..."表示。固定引數和可選引數共同構成的可變引數函式的引數列表。

實現原理

C語言中使用va_list系列變參宏實現變參函式,此處va意為variable-argument(可變引數)。
x86平臺VC6.0整合開發環境中,stdarg.h標頭檔案內變參宏定義如下:

typedef char *va_list;

// 把n 圓整到sizeof(int)的倍數
#define _INTSIZEOF(n)((sizeof(n)+sizeof(int)-1)& ~(sizeof(int)-1))

//初始化 ap指標,使其指向第一個可變引數。v是變參列表的前一個引數
#define va_start(ap,v) (ap = (va_list)&v + _INTSIZEOF(v))

//該宏返回當前變參值,並使ap指向列表中的下一個變參
#define va_arg(ap,type) (*(type *)((ap +=_INTSIZEOF(type) - _INTSIZEOF(type))))

//將指標ap置為無效,結束變參的獲取
#define va_end(ap)  (ap = (va_list)0)

_INTSIZEOF(n)****
_INTSIZEOF宏考慮到某些系統需要記憶體地址對齊。從宏名看應按照sizeof(int)即棧粒度對齊,引數在記憶體中的地址均為sizeof(int) = 4的倍數。
例如,若1<= sizeof(n)<=4,則_INTSIZEOF(n) = 4;
若5<=sizeof(n)<=8,則_INTSIZEOF(n) = 8。

va_start(ap,v)
va_start宏首先根據(va_list)&v得到引數v在棧中的記憶體地址,加上_INTSIZEOF(v)所佔記憶體大小後,使ap指向v的下一個引數。在使用的時候,一般用這個宏初始化ap指標,v是變參列表的前一個引數,即最後一個固定引數,初始化的結果是ap指向第一個變參。

va_arg(ap,type)
這個宏取得type型別的可變引數值。首先ap +=_INTSIZEOF(type),即ap跳過當前可變引數而指向下個變參的地址;然後ap-_INTSIZEOF(type)
得到當前變參的記憶體地址,型別轉換後解引用,最後返回當前變參值。

自己實現一個可變參函式

#include <stdio.h>
//模擬可變引數函式,用於計算多個整數的和
int sum(int count, int first, ...)
{
    int total = first;
    //建立指向第一個可變引數的指標
    int* p = &first;
    //遍歷所有的引數
    for (size_t i = 1; i < count; i++)
    {
        p++;//移動到下一個引數位置
        total += *p;//解引用並新增
    }
    return total;
}
int main()
{
    int result = sum(5,1,2,3,4,5);
    printf("sum::%d\r\n", result);
    return 0;
}

可變參應注意的事項

  • 確保第一個引數指定可變引數的數量和型別
  • 使用正確的型別讀取引數
  • 可變引數的型別一致性
  • 可變引數不能是陣列和結構體
  • 可變參函式不進行型別檢查
  • 小心浮點型別的預設提升
  • 避免在迴圈中使用可變引數
  • 使用標準庫宏來避免手動解析

相關文章