1. 程式人生 > > vsprintf函式和va_list用法詳解

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"之類的都是人為規定的,得自己程式設計識別這些格式。

.