可變引數列表及printf函式的實現

2021-06-01 10:35:20 字數 1724 閱讀 1015

一直沒有搞懂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...