我們以 printf 這個 very 熟悉的函數為例,來分析一下變參函數。先看下 printf 函數的定義: ~~~~ int printf(const char fmt, ...) { int i; int len; / va_list 即 char / va_list args; va_star ...
我們以 printf 這個 very 熟悉的函數為例,來分析一下變參函數。先看下 printf 函數的定義:
int printf(const char *fmt, ...)
{
int i;
int len;
/* va_list 即 char * */
va_list args;
va_start(args, fmt);
/* 內部使用了 va_arg() */
len = vsprintf(g_PCOutBuf,fmt,args);
va_end(args);
for (i = 0; i < strlen(g_PCOutBuf); i++)
{
putc(g_PCOutBuf[i]);
}
return len;
}
什麼是變參函數?
可變參數函數的原型聲明為 type VAFunction(type arg1, type arg2, … );
參數可以分為兩部分:個數確定的固定參數和個數可變的可選參數。函數至少需要一個固定參數,固定參數的聲明和普通函數一樣;可選參數由於個數不確定,聲明時用 "..." 表示。固定參數和可選參數共同構成一個函數的參數列表。
printf 函數涉及了以下幾個重要的巨集:
typedef char * va_list;
/*
* Storage alignment properties
*/
#define _AUPBND (sizeof (acpi_native_int) - 1) //acpi_native_int 為 4 位元組(根據機器字長而定)
#define _ADNBND (sizeof (acpi_native_int) - 1)
/*
* Variable argument list macro definitions
*/
#define _bnd(X, bnd) (((sizeof (X)) + (bnd)) & (~(bnd)))
#define va_arg(ap, T) (*(T *)(((ap) += (_bnd (T, _AUPBND))) - (_bnd (T,_ADNBND))))
#define va_end(ap) (void) 0
#define va_start(ap, A) (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND))))
分析以下三個巨集的作用
1) va_start 巨集
作用: 根據 A 取得可變參數表的首指針並賦值給 ap。
原理: 根據最後一個固定參數 A 的地址 + 第一個變參對 A 的偏移地址,然後賦值給 ap,這樣 ap 就是可變參數表的首地址(函數傳遞的參數會從右向左依次入棧,並且 ARM 的棧為降棧,所以參數 A 的地址最低)。
2) va_arg 巨集
作用: 指取出當前 ap 所指的可變參數並將 ap 指針指向下一可變參數。
3) va_end 巨集
作用: 結束可變參數的獲取,與 va_start 對應使用。
堆棧中,各個函數的分佈情況是倒序的,即最後一個參數在列表中地址最高部分,第一個參數在列表地址的最低部分。參數在堆棧中的分佈情況如下:
*******************
最後一個參數
倒數第二個參數
...
第一個參數
函數返回地址
函數代碼段
*******************
得到可變參數個數的三種辦法:
1) 函數的第一個參數,指定後續的參數個數,如 func(int num,...);
2) 根據隱含參數,判斷參數個數,如 printf 系列的,通過字元串中 % 的個數判斷;
3) 特殊情況下(如參數都是不大於 0xFFFF 的 int),可以一直向低處訪問堆棧,直到返回地址。
而 _bnd(X, bnd) 巨集就是以 4 位元組對齊的變數 X 的大小。
自己實現一個簡單的 printf 函數
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
void print(char *fmt, ...)
{
va_list ptr;
va_list temp_ptr = NULL;
/* 用於存儲最終轉換的結果 */
char buf[100] = {0};
/* 用於存儲臨時轉換的結果 */
char temp_buf[50] = {0};
unsigned char index = 0;
unsigned char len, arg_len;
len = strlen(fmt);
/* 得到可變參數的首地址 */
va_start(ptr, fmt);
for(; *fmt; fmt++){
if(*fmt == '%'){
switch(*++fmt){
case 'd':
case 'D':
sprintf(temp_buf, "%d", va_arg(ptr, int));
temp_ptr = temp_buf;
break;
case 's':
case 'S':
/* 取出當前變數,並將指針指向下一個可變參數 */
temp_ptr = va_arg(ptr, char*);
break;
}
arg_len = strlen(temp_ptr);
strcat(buf+index, temp_ptr);
index += arg_len;
}else{
buf[index] = *fmt;
index++;
}
}
/* 結束取參 */
va_end(ptr);
/* 輸出最終轉換結果 */
puts(buf);
}
int main()
{
print("My name is %s and my height is %d cm.", "Lance#", 178);
return 0;
}
程式運行結果:
My name is Lance# and my height is 178 cm.