c可變引數詳解

2021-08-31 13:50:09 字數 3730 閱讀 8329

最近翻到今年前自己寫的hello word 的劣質**。突然看見printf,這個可變引數的函式。而平時所編寫的都是固定引數。所以今天一步步德了解下可變引數函式的實現原理。

編寫乙個自己寫的printf()函式。

1.可以通過…三個點表示可變引數

2.函式的引數是通過棧進行 。棧是由高到低來儲存,而且是從右往左讀入。函式傳遞過程就是壓棧的過程。如test(int a,int b,int c),先讀入c,再b再a。所以,如圖c的位址是最高的。

但發現了沒有,上面的位址,不管你是int還是char,位址都是4個位元組來排列。char不是只佔乙個位元組嗎。其實,在記憶體中,位址是不能隨便訪問的。比如在x86系統中,只能訪問4位元組倍數的位址,這就是記憶體位元組對齊。位元組對齊是計算機原理和架構問題,這裡就不詳解了。

函式傳入的可變引數是不固定的。那麼如何訪問這些引數呢??怎麼獲得引數的位址?

上面介紹了 引數傳遞是乙個棧。棧遵守先進後出。那麼,傳入的引數1(上面的a)就是棧頂,而c就是棧底。只要知道剛開始傳入時候棧底的位址,最後的棧頂位址以及傳入引數型別,通過指標的移位可以獲得各個引數的位址。

void

dbg_print

(const

char

* fmt,..

.)

它就是乙個char *的重新命名

typedef

char

* va_list

va_start()用來初始化ap,先看看它的定義。(stdarg.h)

#define va_start __crt_va_start
沒啥用,還是定義,再看看 __crt_va_start定義

第一層定義

#define __crt_va_start(ap, x) ((void)(__vcrt_va_start_verify_argument_type(), __crt_va_start_a(ap, x)))
這個就挺有意思的了,又定義成了兩部分。

第一層定義的第一部分

__vcrt_va_start_verify_argument_type()函式實現如下,原來是關鍵字static_assert,用來靜態斷言,其語法很簡單:static_assert(常量表示式,提示字串)。如果第乙個引數常量表示式的值為真(true或者非零值),那麼static_assert不做任何事情,就像它不存在一樣,否則會產生一條編譯錯誤,錯誤位置就是該static_assert語句所在行,錯誤提示就是第二個引數提示字串。

void

__vcrt_va_start_verify_argument_type()

throw()

而常量表示式如下,是一些結構體模版。賦值語義就是false,取反就是true,為真就不做任何事。而如果是引用或者move語義,就會false,編譯器就會報錯。

template 

struct __vcrt_va_list_is_reference

;}; template

struct __vcrt_va_list_is_reference<_ty>

>;}

; template

struct __vcrt_va_list_is_reference<_ty>

>;}

;

我試一下,用引用傳入,果然如此

== 總結:原來,第一層定義的第一部分就是靜態斷言。用來編譯器報錯,沒啥用。==

第一層定義的第二部分

__crt_va_start_a(ap, x),又是兩部分,悲傷。

#define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_addressof(v) + _intsizeof(v)))
第一層定義的第二部分的第一部分
#define _addressof(v)   (&const_cast(reinterpret_cast(v)))
1–const_cast(expression)

const_cast用來將const指標或者const引用修改為非const指標或者引用。如將expression強制轉換為type_id型別的非const引用。

2–reinterpret_cast (expression)

reinterpret_cast用來將乙個指標轉換成乙個整數或者指標等。如將expression指標或者 引用等轉換為type-id型別的(整數/指標)

第一層定義的第二部分的第一部分意思就是將v強制轉換為char型別的引用,再將這個char型別的引用去掉const,轉換為非const的char的引用。

總結:原來第一層定義的第二部分的第一部分就是將v強制型別轉換非const的char型別引用,也沒啥用。

第一層定義的第二部分的第二部分

((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))就是核心了,它是乙個位演算法。作用:計算n的下乙個引數的記憶體對齊位址。具體數學原理就不解釋了。如x86對齊是4位元組的倍數,n是傳入的第乙個引數,如n是char =1, 1+3 & ~3-》 0100&1100 =4位元組。如果n是char * 指標為4位元組所以 n=4, 4+3& ~3-> 0111& 1100=4位元組。所以。這個演算法不管你第乙個引數是什麼型別,都能獲得第二個引數的相對位址。

#define _intsizeof(n)          ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))
1 它會先靜態斷言,判斷是否有錯

2 它會強制轉換

3 它會計算第二個引數相對位址

4 將強制轉換的v+相對位址,賦值給pt。也就是第二個引數的位址。

使用引數列表傳送格式化輸出到字串。

栗子vsprintf()第乙個引數是接收的陣列。第二個引數是格式化字串,第三個是待格式化的引數。如傳入2018,11,11引數,2018當做fmt去了,所以ap就是後面的第二個引數(11)開始的指標,會將11,11,傳入%d 中。

va_end用來釋放ap

#define va_end   __crt_va_end

#define __crt_va_end(ap) ((void)(ap = (va_list)0))

所以,回過頭來看**, 是不是很簡單。

dbg_print

("hello %d"

,2018);

void

dbg_print

(const

char

* fmt,..

.)

C 可變引數

include c 對於函式支援可變形參,它通過 來表示未知個數的形參,通過va 系列函式進行操作。va list是乙個巨集,由va start和va end界定。typedef char va list void va start va list ap,prev param type va arg...

C 可變引數

寫 之前要先介紹一下可變引數的備用知識 c函式要在程式中用到以下這些巨集 void va start va list arg ptr,prev param type va arg va list arg ptr,type void va end va list arg ptr va在這裡是varia...

C 可變引數

1.概念 常用如printf函式 它的引數並不是固定個數的,而在呼叫時根據情況確定 2.c中的實現 用到了 va list 每次指向乙個可變的引數 va start 初始化va list變數,使其指向第乙個可變引數 va end 清理 3.書上的兩個簡單例子 1 int print arg int ...