vsprintf函式和va_list用法詳解
void UART1_Printf(char *fmt,...)
{
va_list ap;
char string[128];
va_start(ap, fmt);
vsprintf(string, fmt, ap);
UART1_SendString(string);
va_end(ap);
}
不解。查找了一下用法,記錄如下:
VA_LIST的用法( VA_LIST 是在C語言中解決變參問題的一組巨集):
(1)首先在函式裡定義一具VA_LIST型的變數,這個變數是指向引數的指標
(2)然後用VA_START巨集初始化變數剛定義的VA_LIST變數,這個巨集的第二個
引數是第一個可變引數的前一個引數,是一個固定的引數。(如在執行VA_START
(ap,v)以後,ap指向第一個可變引數在堆疊的地址。)
(3)然後用VA_ARG返回可變的引數,VA_ARG的第二個引數是你要返回的引數的型別。
(4)最後用VA_END巨集結束可變引數的獲取。然後你就可以在函式裡使用第二個引數了。
如果函式有多個可變引數的,依次呼叫VA_ARG獲取各個引數。
函式名: vsprintf
功 能: 送格式化輸出到串中
用 法: int vsprintf(char *string, char *format, va_list param);
程式例:
#include
#include
#include
char buffer[80];
int vspf(char *fmt, ...)
{
va_list argptr;
int cnt;
va_start(argptr, fmt);
cnt = vsprintf(buffer, fmt, argptr);
va_end(argptr);
return(cnt);
}
int main(void)
{
int inumber = 30;
float fnumber = 90.0;
char string[4] = "abc";
vspf("%d %f %s", inumber, fnumber, string);
printf("%s\n", buffer);
return 0;
}
一直沒有搞懂printf函式是怎麼實現的,今天又看了一下可變引數列表的函式的編寫,大概的瞭解了一點。反推出原來沒想到的一個東西,那就是函式形參列表中的變數在記憶體中的位置是順次排列的。
標頭檔案Stdarg.h裡的幾個巨集定義就是利用了這麼一點,順次獲取多個引數,感覺還是挺笨的一個方法。
先看一個最簡單的可變引數列表的函式:
void mytest(int a, ...)
{
int b;
va_list ap;
va_start(ap, a);
b = va_arg(ap, int);
va_end(ap);
printf("%d, %d", a, b);
}
函式實現列印數字。關鍵問題是搞懂va_list,va_start,va_arg,va_end幾個的意思。
首先是在ADS下寫的程式,開啟stdarg是這個樣子:
typedef int *va_list[1];
#define va_start(ap, parmN) (void)(*(ap) = __va_start(parmN))
#define va_arg(ap, type) __va_arg(*(ap), type)
#define va_end(ap) ((void)(*(ap) = 0))
實在找不到__va_start,__va_arg是怎麼實現的,只好到keil下面去找,這裡的就簡單多了:
typedef char *va_list;
#define va_start(ap,v) ap = (va_list)&v + sizeof(v)
#define va_arg(ap,t) (((t *)ap)++[0])
#define va_end(ap)
原來每個平臺下面的Stdarg標頭檔案的定義都是不相同的。就拿keil那裡的來入手。
1. 首先定義一個va_list型的變數ap,也就是char *。
2. va_start(ap, a)巨集替換之後就是ap = (va_list)&a + sizeof(a);
首先取a的地址,即第一個固定引數的地址,然後強制型別轉換為va_list,接著後移a的記憶體大小,把當前這個地址值賦給ap。很明顯,就是第一個引數a後面的那個地方,按照上面說的,也就是第一個可變引數。即現在把ap指向第一個可變引數。
3. b = va_arg(ap, int)巨集替換為b = ((int *)ap)++[0];
自加在後,因此是獲取第一個引數的值賦給b,然後ap後移一個型別的位置,即指向下一個元素的地址。
4. va_end(ap),這裡什麼都沒有做,在ADS那個版本里是將ap指向NULL,防止誤操作。
那麼按照這種做法,當用mytest(100, 200);呼叫這個函式時,它將可以打印出100,200。
如果用mytest(100,200,300);呼叫這個函式,仍然只是顯示100,200。如果要將300打印出來,那麼得再使用一次va_arg巨集,把它賦值給變數c,把c打印出來。
所以stdarg裡的幾個巨集只是機械的把每個引數讀出來,它甚至不知道現在讀的是什麼型別的變數,如果要讀下一個變數的話得往後跳多少位置,這些都只能靠程式自己來判斷指示。
所以printf裡的格式符號"%d,%s,%c"之類的都是人為規定的,得自己程式設計識別這些格式。
