C 可變參數函數的本質


C語言支持定義可變參數的函數,方法是在函數的參數列表最后加上 " ... ",代表變長的參數列表,例如:

void Func(int num, ...) {  }

需要注意 “...” 必須在最后,而且前面起碼要有一個固定的參數,類型可以任意。

為什么要有一個固定的參數呢?這篇文章要說明的就是這個問題。

 

首先我們是如何調用變長參數列表里的變量?

需要使用 stdarg.h 里定義的三個宏:va_start(ap, x)、va_arg(ap,t)、va_end(ap),還有一個va_list類型(本質上是字節指針)

這幾個宏的源代碼:

1  typedef char* va_list; 2 
3  #define _INTSIZEOF(n)          ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))
4 
5  #define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))
6  #define __crt_va_arg(ap, t)     (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
7  #define __crt_va_end(ap)        ((void)(ap = (va_list)0))

 

va_start用於獲取變長參數列表的起始地址。

使用方法是:

  1. 定義一個va_list類型變量,例如vlist.
  2. 使用宏 va_start(vlist, 最后一個固定參數) 獲取變長列表的起始地址
va_list vlist; vlist = va_start(vlist, num);

這個宏本質上是獲取固定參數(如num)的下一個參數地址。原理是調用函數時,程序會將函數參數逐個壓入棧中,使參數連續排列在內存中,因此只需要知道上一參數的內存地址和它的類型,就可以算出下一參數的地址。

因此這個宏等價於:vlist = (char*)&num + sizeof(num);

 

va_arg用於按順序獲取下一個參數。

使用方法:

Type value = va_arg(vlist, Type);

本質上是對變長參數列表指針加sizeof(Type),返回累加前的地址指向的值。等價於:

Type value = *(Type*)vlist; vlist += sizeof(Type);

 

va_end非常簡單,就是把變長參數列表的指針置0,防止可能的錯誤。等價於:

vlist = (char*)0;

 

最后的簡單總結:

之所以要有一個固定參數,是因為只有知道最后一個參數的地址,才能獲取變長列表開始的地址。

此外需要注意的是,在不同平台,不同編譯器里,由於內存排列有所差別(內存對齊的差別),實際情況不一定有上面寫的等效代碼一樣簡單。具體可以查看vadefs.h里的定義。

 1 #ifdef __cplusplus
 2     #define _ADDRESSOF(v) (&const_cast<char&>(reinterpret_cast<const volatile char&>(v)))
 3 #else
 4     #define _ADDRESSOF(v) (&(v))
 5 #endif
 6 
 7 #if (defined _M_ARM || defined _M_HYBRID_X86_ARM64) && !defined _M_CEE_PURE
 8     #define _VA_ALIGN       4
 9     #define _SLOTSIZEOF(t)  ((sizeof(t) + _VA_ALIGN - 1) & ~(_VA_ALIGN - 1))
10     #define _APALIGN(t,ap)  (((va_list)0 - (ap)) & (__alignof(t) - 1))
11 #elif defined _M_ARM64 && !defined _M_CEE_PURE
12     #define _VA_ALIGN       8
13     #define _SLOTSIZEOF(t)  ((sizeof(t) + _VA_ALIGN - 1) & ~(_VA_ALIGN - 1))
14     #define _APALIGN(t,ap)  (((va_list)0 - (ap)) & (__alignof(t) - 1))
15 #else
16     #define _SLOTSIZEOF(t)  (sizeof(t))
17     #define _APALIGN(t,ap)  (__alignof(t))
18 #endif
19 
20 #if defined _M_CEE_PURE || (defined _M_CEE && !defined _M_ARM && !defined _M_ARM64)
21 
22     void  __cdecl __va_start(va_list*, ...);
23     void* __cdecl __va_arg(va_list*, ...);
24     void  __cdecl __va_end(va_list*);
25 
26     #define __crt_va_start_a(ap, v) ((void)(__va_start(&ap, _ADDRESSOF(v), _SLOTSIZEOF(v), __alignof(v), _ADDRESSOF(v))))
27     #define __crt_va_arg(ap, t)     (*(t *)__va_arg(&ap, _SLOTSIZEOF(t), _APALIGN(t,ap), (t*)0))
28     #define __crt_va_end(ap)        ((void)(__va_end(&ap)))
29 
30 #elif defined _M_IX86 && !defined _M_HYBRID_X86_ARM64
31 
32     #define _INTSIZEOF(n)          ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))
33 
34     #define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))
35     #define __crt_va_arg(ap, t)     (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
36     #define __crt_va_end(ap)        ((void)(ap = (va_list)0))
37 
38 #elif defined _M_ARM
39 
40     #ifdef __cplusplus
41         void __cdecl __va_start(va_list*, ...);
42         #define __crt_va_start_a(ap, v) ((void)(__va_start(&ap, _ADDRESSOF(v), _SLOTSIZEOF(v), _ADDRESSOF(v))))
43     #else
44         #define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _SLOTSIZEOF(v)))
45     #endif
46 
47     #define __crt_va_arg(ap, t) (*(t*)((ap += _SLOTSIZEOF(t) + _APALIGN(t,ap)) - _SLOTSIZEOF(t)))
48     #define __crt_va_end(ap)    ((void)(ap = (va_list)0))
49 
50 #elif defined _M_HYBRID_X86_ARM64
51     void __cdecl __va_start(va_list*, ...);
52     #define __crt_va_start_a(ap,v) ((void)(__va_start(&ap, _ADDRESSOF(v), _SLOTSIZEOF(v), __alignof(v), _ADDRESSOF(v))))
53     #define __crt_va_arg(ap, t)    (*(t*)((ap += _SLOTSIZEOF(t)) - _SLOTSIZEOF(t)))
54     #define __crt_va_end(ap)       ((void)(ap = (va_list)0))
55 
56 #elif defined _M_ARM64
57 
58     void __cdecl __va_start(va_list*, ...);
59 
60     #define __crt_va_start_a(ap,v) ((void)(__va_start(&ap, _ADDRESSOF(v), _SLOTSIZEOF(v), __alignof(v), _ADDRESSOF(v))))
61     #define __crt_va_arg(ap, t)                                                 \
62         ((sizeof(t) > (2 * sizeof(__int64)))                                   \
63             ? **(t**)((ap += sizeof(__int64)) - sizeof(__int64))               \
64             : *(t*)((ap += _SLOTSIZEOF(t) + _APALIGN(t,ap)) - _SLOTSIZEOF(t)))
65     #define __crt_va_end(ap)       ((void)(ap = (va_list)0))
66 
67 
68 #elif defined _M_X64
69 
70     void __cdecl __va_start(va_list* , ...);
71 
72     #define __crt_va_start_a(ap, x) ((void)(__va_start(&ap, x)))
73     #define __crt_va_arg(ap, t)                                               \
74         ((sizeof(t) > sizeof(__int64) || (sizeof(t) & (sizeof(t) - 1)) != 0) \
75             ? **(t**)((ap += sizeof(__int64)) - sizeof(__int64))             \
76             :  *(t* )((ap += sizeof(__int64)) - sizeof(__int64)))
77     #define __crt_va_end(ap)        ((void)(ap = (va_list)0))
78 
79 #endif
vadefs.h 部分代碼

 

知道了原理,我們其實可以直接獲取變長參數列表里任意一個變量,而不用逐個獲取,特別是在參數的類型都相同的情況下,例如:

 1 int Sum(int count, ...)  2 {  3     int sum = 0;  4 
 5     for (int i = 0; i < count; i++)  6  {  7         sum += *(int *)((char *)&count + sizeof(int) * (i + 1));  8  }  9 
10     return sum; 11 }

當然,這樣的代碼移植性差,如果更改了平台很可能就會出錯,使用時還是謹慎為好。

此外還有一些陷阱:

https://blog.csdn.net/smstong/article/details/50751121 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM