一直沒有搞懂printf函式是怎麼實現的,今天又看了一下可變引數列表的函式的編寫,大概的了解了一點。反推出原來沒想到的乙個東西,那就是函式形參列表中的變數在記憶體中的位置是順次排列的。
標頭檔案stdarg.h裡的幾個巨集定義就是利用了這麼一點,順次獲取多個引數,感覺還是挺笨的乙個方法。
先看乙個最簡單的可變引數列表的函式:
void mytest(
int a,..
.)函式實現列印數字。關鍵問題是搞懂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"之類的都是人為規定的,得自己程式設計識別這些格式。嘗試寫乙個相對簡單的prinrf函式:
再看linux下的實現:
void printf(
const char *format,..
.)由此可見,其其實只是先越過format,然後依次取出引數列表給vsnprintf。
可變引數列表 簡單printf函式的實現
有時候形式引數不確定,ansi c採用可變引數列表的形式來實現這種函式,在stdarg.h中包括三個巨集 va list va start va list,pre arg va arg va list,type va end va list 作用分別是 建立乙個指標指向函式的最後乙個引數 取出typ...
main函式引數及可變引數列表
c語言中main函式是程式的入口函式,一般在使用main函式的時候都是不帶引數的,那麼main函式帶上引數又如何呢?main函式形式 int main int argc,char argv,char envp 可知main函式有三個引數 1,argc 記錄命令列引數的個數 包含第乙個引數 可執行檔名...
可變引數 函式 可變引數列表 1
我們在c語言程式設計中有時會遇到一些引數個數可變的函式,即函式的入參個數和型別是不確定的,例如printf 函式,其函式原型為 int printf char format,它除了有乙個引數format固定以外,後面跟的引數的個數和型別是可變的 用三個點 做引數佔位符 實際呼叫時可以有以下的形式 p...