采用C語言編程的時候,函數中形式參數的數目通常是確定的,在調用時要依次給出與形式參數對應的所有實際參數。但在某些情況下希望函數的參數個數可以根據需要確定。典型的例子有大家熟悉的函數printf()、scanf()和系統調用execl()等。那麼它們是怎樣實現的呢?
C編譯器通常提供了一系列處理這種情況的宏,以屏蔽不同的硬件平台造成的差異,增加程序的可移植性。這些宏包括va_start、va_arg和va_end等。在講解以上宏之前我們先了解一下調用函數時傳入參數的處理過程。
一、函數傳入參數過程
一個函數包括函數名、傳入參數、返回參數以及函數體,函數體編譯後的二進制代碼儲存在程序代碼區。當用戶調用某個函數時,系統會通過函數名(C++中會涉及到mangled命名處理)查找函數體入口指針並壓入棧中,之後將傳入參數以從右至左的順序壓入棧中(棧空間是往低地址方向增長的,也即棧底對應高地址,棧頂對應低地址),示例如下:
#include <iostream>
using namespace std;
void fun(int a, ...)
{
int *temp = &a;
temp++;
for (int i = 0; i < a; ++i) { cout<< *temp << endl;
temp++;
}
}
int main()
{
int a = 1;
int b = 2;
int c = 3;
int d = 4;
fun(4, a, b, c, d);
system("pause");
return 0;
}
// Output:
// 1
// 2
// 3
// 4
二、va_start、va_arg和va_end宏定義
接著我們再來看看va_start、va_arg和va_end等宏的具體定義。
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) // 此句宏的作用是將類型n的大小向上取成4的倍數,如n為char型的話結果即為4
#ifdef __cplusplus
#define _ADDRESSOF(v) ( &reinterpret_cast<const char &>(v) ) // vs2015中此句高亮,作用是將v的地址重新解釋成char*型
#else
#define _ADDRESSOF(v) ( &(v) )
#endif
#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 )
#ifndef _VA_LIST_DEFINED
#ifdef _M_CEE_PURE
typedef System::ArgIterator va_list;
#else
typedefchar * va_list; // vs2015中此句高亮
#endif /* _M_CEE_PURE */
#define _VA_LIST_DEFINED
#endif
// 以上宏定義出現在vadef.h,通過stdio.h即可使用
#define va_start _crt_va_start
#define va_arg _crt_va_arg
#define va_end _crt_va_end
// 以上宏定義出現在stdarg.h中,若要使用則需加上 #include <stdarg.h>
三、va_start、va_arg和va_end使用示例
容易看出,以上宏定義主要涉及地址操作,va_start獲取第二個傳入參數的地址給va_list類型變量(假設為arg_ptr),va_arg用於獲取當前參數值並將指針arg_ptr往後移,va_end則是將arg_ptr置為空。va_start、va_arg、va_end具體用法示例如下:
#include <stdio.h>
#include<stdarg.h>
int fun(int x, int y) {
return x - y;
}
int fun(int count, ...) {
va_list arg_ptr; // 等同於 char *arg_ptr;
int nArgValue = count;
int nArgCout = 0;
va_start(arg_ptr, count); // 使arg_ptr指向第二個參數的地址
printf("The 1 th arg: %d\n", nArgValue); // 輸出第一個參數的值
int sum = 0;
for (int i = 0; i < count; i++)
{
++nArgCout;
nArgValue= va_arg(arg_ptr, int);// 將arg_ptr所指參數返回成int並移動arg_ptr使其指向後一個參數,這裡假設傳入參數均是int型
printf("The %d th arg: %d\n", i+2, nArgValue); // 輸出各參數的值
sum += nArgValue;
}
va_end(arg_ptr); // 將arg_ptr置為空
return sum;
}
int main() {
cout<< fun(0) << endl;
cout<< fun(1, 1) << endl; // 優先匹配到函數int fun(int, int), 輸出0
cout << fun(2, 3, 4) << endl;
system("pause");
return 0;
}
// Output:
// 0
// 0
// The 1 th arg: 2
// The 2 th arg: 3
// The 3 th arg: 4
// 7
注意:以上宏操作並不提供參數個數獲取操作,這需要用戶在函數中獲取,如第二個fun函數使用count指明個數,printf通過解析第一個傳入參數來確定參數個數與類型等。