va_list/va_start/va_arg/va_end深入分析
va_list/va_start/va_arg/va_end這幾個巨集,都是用於函式的可變引數的。
我們來看看在vs2008中,它們是怎麼定義的:
1: ///stdarg.h
2: #define va_start _crt_va_start
3: #define va_arg _crt_va_arg
4: #define va_end _crt_va_end
5:
6: ///vadefs.h
7: #define _ADDRESSOF(v) ( &reinterpret_cast<const char &>(v) )
8: typedef char * va_list;
9: #define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
10: #define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
11: #define _crt_va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
12: #define _crt_va_end(ap) ( ap = (va_list)0 )
再看看各個巨集的功能是什麼?
- va_list用於宣告一個變數,我們知道函式的可變引數列表其實就是一個字串,所以va_list才被宣告為字元型指標,這個型別用於宣告一個指向引數列表的字元型指標變數,例如:va_list ap;//ap:arguement pointer
- va_start(ap,v),它的第一個引數是指向可變引數字串的變數,第二個引數是可變引數函式的第一個引數,通常用於指定可變引數列表中引數的個數。
- va_arg(ap,t),它的第一個引數指向可變引數字串的變數,第二個引數是可變引數的型別。
- va_end(ap) 用於將存放可變引數字串的變數清空(賦值為NULL).
我們看一段具有可變引數列表的函式來求陣列和的程式碼:
1: /*
2: *
3: *功能: 巨集va_arg()用於給函式傳遞可變長度的引數列表。
4: *首先,必須呼叫va_start() 傳遞有效的引數列表va_list和函式強制的第一個引數。第一個引數代表將要傳遞的引數的個數。
5: *其次,呼叫va_arg()傳遞引數列表va_list 和將被返回的引數的型別。va_arg()的返回值是當前的引數。
6: *再次,對所有的引數重複呼叫va_arg()
7: *最後,呼叫va_end()傳遞va_list對完成後的清除是必須的。
8: *
9: *時間:2011年8月17日22:34:04
10: *作者:張超
11: *Email:uestczhangchao@gmail.com
12: *
13: */
14:
15:
16: #include "X:\程式設計練習\C-C++\global.h"
17:
18: #if va_arg==stdon
19: #include <stdio.h>
20: #include <stdarg.h>
21: #include <stdlib.h>
22:
23: //第一個引數指定了引數的個數
24: int sum(int number,...)
25: {
26: va_list vaptr;
27: int i;
28: int sum = 0;
29: va_start(vaptr,number);
30: for(i=0; i<number;i++)
31: {
32: sum += va_arg(vaptr,int);
33: }
34: va_end(vaptr);
35: return sum;
36: }
37:
38:
39: int main()
40: {
41: printf("%d\n",sum(4,4,3,2,1));
42: system("pause");
43: return 0;
44: }
45:
46: #endif
- va_start的功能是要把,ap指標指向可變引數的第一個引數位置處,
#define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
先取第一個引數的地址,在sum函式中就是取number的地址並且將其轉化為char *的(因為char *的指標進行加減運算後,偏移的位元組數才與加的數字相同, 如果為int *p,那麼p+1實際上將p移動了4個位元組),然後加上4(__INITSIZEOF(number)=(4+3)&~3),這樣就將ap指向了可變引數字串的第一個引數。
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
|
- va_arg是要從ap中取下一個引數。
#define _crt_va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
對於這個巨集,哥糾結了很久,最後終於搞清楚了,究其原因就是自己C語言功底不紮實,具體表現在沒有搞清楚賦值表示式的值是怎麼運作的。
我們看這個巨集,首先是ap = ap + __INTSIZEOF(t)。注意到,此時ap已經被改變了,它已經指向了下一個引數,我們令x=ap + __INTSIZEOF(t);
那麼括號內就變成了(x – __INTSIZEOF(t)),但是這裡沒有賦值運算子,所以ap的值沒有發生變化,此時ap仍然指向的是當前引數的下一個引數的位置,
也就是說ap指向的位置比當前正在處理的位置超前了一個位置。
其實寫成下面的形式就簡單明瞭了:
#define va_arg(ap,t) (*(t *)((ap += _INTSIZEOF(t)), ap - _INTSIZEOF(t)) )
分析:為什麼要將ap指向當前處理引數的下一個引數了?
經過上面的分析,我們知道va_start(ap,v)已經將ap指向了可變引數列表的第一個引數了,以後我們每一步操作都需要將ap移動到下一個
引數的位置,由於我們每次使用可變引數的順序是:va_start(ap,v)—>va_arg(ap,t);這樣我們在第一次去引數的時候,其實ap已經指向了
第二個引數開始的位置,所以我們用表示式的方式獲得一個指向第一個引數的臨時指標,這樣我們就可以採用這種一致的方式來處理可變引數列表。
(感覺沒表達的十分清楚,希望各位朋友糾正~~~~~~)。
下圖是我的例子程式中去引數的情況(時間倉促,畫得很醜,請原諒):
- va_end(ap) 將宣告的ap指標置為空,因為指標使用後最後設定為空。
參考資料:
相關文章
- C語言使用函式引數傳遞中的省略號:va_list, va_start, va_arg, va_endC語言函式
- AArch64中va_list/va_start/va_arg/...的實現
- va_start和va_end使用詳解
- C 可變引數函式分析(va_start,va_end,va_list...)函式
- va_list 原理以及用法
- TThread深入分析thread
- 深入分析 Fiesta Exploit Kit
- 深入分析 Golang 的 ErrorGolangError
- Android動畫深入分析Android動畫
- 深入分析Session和CookieSessionCookie
- SPI機制深入分析
- Python MetaClass深入分析Python
- 深入分析C++引用C++
- 深入分析 Hello World 程式
- 深入分析 Docker 映象原理Docker
- ATL Thunk機制深入分析
- 深入分析 synchronized 關鍵字synchronized
- 深入分析HTTP代理的原理HTTP
- 深入分析 Javac 編譯原理Java編譯原理
- Redis API & Java RedisTemplate深入分析RedisAPIJava
- MySQL latch爭用深入分析MySql
- 深入分析JVM執行引擎JVM
- 深入分析CAS(樂觀鎖)
- 深入分析KubernetesCriticalPod(二)
- 深入分析MVC、MVP、MVVM、VIPERMVCMVPMVVM
- Buffer Busy Waits深入分析AI
- TLB與cache的深入分析
- RACSignal 的 Subscription 深入分析
- Go 包管理機制深入分析Go
- Dart Sound Null Safety 深入分析DartNull
- 深入分析 Flutter 初始化流程Flutter
- 深入分析kube-batch(4)——actionsBAT
- Redis記憶體碎片深入分析Redis記憶體
- 深入分析LRU與DIRTY LIST(轉)
- 深入分析Oracle日誌檔案Oracle
- 【MySQL】資料庫事務深入分析MySql資料庫
- [譯] 深入分析 Angular 變更檢測Angular
- ijkplayer-丟幀策略深入分析