1. 使用情形
1
2
3
4
5
6
7
8
|
int
a =10; double
b = 20.0; char
*str = "Hello
world" ; printf ("begin
print "); printf ("a=%d,
b=%.3f, str=%s ",
a, b, str); ... |
2. printf 函数的实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
//acenv.h typedef
char
* va_list ; #define _AUPBND (sizeof
(acpi_native_int) - 1) #define _ADNBND (sizeof
(acpi_native_int) - 1) #define
_bnd(X, bnd) (((sizeof (X)) + (bnd)) & (~(bnd))) #define
va_arg(ap, T) (*(T *)(((ap) += (_bnd (T, _AUPBND))) - (_bnd (T,_ADNBND)))) #define
va_end(ap) (void) 0 #define
va_start(ap, A) (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND)))) //start.c static
char
sprint_buf[1024]; int
printf ( char
*fmt, ...) { va_list
args; int
n; va_start (args,
fmt); n
= vsprintf (sprint_buf,
fmt, args); va_end (args); write(stdout,
sprint_buf, n); return
n; } //unistd.h static
inline
long
write( int
fd, const
char
*buf, off_t count) { return
sys_write(fd, buf, count); } |
从上面的代码来看,printf似乎并不复杂,它通过一个宏va_start把所有的可变参数放到了由args指向的一块内存中,然后再调用vsprintf. 真正的参数个数以及格式的确定是在vsprintf搞定的了。由于vsprintf的代码比较复杂,也不是我们这里要讨论的重点,所以下面就不再列出了。我们这里要讨论的重点是va_start(ap, A)宏的实现,它对定位从参数A后面的参数有重大的制导意义。现在把 #define va_start(ap, A) (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND)))) 的含义解释一下如下:
1
2
3
4
|
va_start (ap,
A) {
char
*ap = (( char
*)(&A)) + sizeof (A)并 int 类型大小地址对齐 } |
1
2
3
4
5
6
|
void
fun( double
d,...) { va_list
args; int
n; va_start (args,
d); } |
1
|
char
*args = ( char
*)&d + sizeof ( double ); |
可变参数函数的实现与函数调用的栈结构有关,正常情况下c/c++的函数参数入栈规则为__stdcall, 它是从右到左的,即函数中的最右边的参数最先入栈。对于函数
1
2
3
4
5
|
void
fun( int
a, int
b, int
c) { int
d; ... } |
1
2
3
4
|
0x1ffc-->d 0x2000-->a 0x2004-->b 0x2008-->c |
1
2
3
4
|
0x1ffc-->a (4字节) 0x2000-->b (4字节) 0x2004-->c (8字节) 0x200c-->d (4字节) |
如果知道了参数a的地址,则要取后续参数的值则可以通过a的地址计算a后面参数的地址,然后取对应的值,而后面参数的个数可以直接由变量a指定,当然也可以像printf一样根据第一个参数中的%模式个数来决定后续参数的个数和类型。如果参数的个数由第一个参数a直接决定,则后续参数的类型如果没有变化并且是已知的,则我们可以这样来取后续参数, 假定后续参数的类型都是double;
1
2
3
4
5
6
7
8
|
void
fun1( int
num, ...) { double
*p = ( double
*)((&num)+1); double
Param1 = *p; double
Param2 = *(p+1); ... double
Paramn *(p+num); } |
1
2
3
4
|
i--- int s--- signed
short l--- long c--- char |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
void
printf ( char
*fmt, ...) { char
s[80] = "" ; int
paramCount = strlen (fmt); write(stdout,
"paramCount
= "
, strlen (paramCount
= )); itoa(paramCount,s,10); write(stdout,
s, strlen (s)); char
*p = ( char
*)(&fmt) + sizeof ( char
*); int
*pi = ( int
*)p; for
( int
i=0; i<paramCount; i++) { char
line[80] = "" ; strcpy (line,
"param" ); itoa(i+1,
s, 10); strcat (line,
s); strcat (line,
"=" ); switch (fmt[i]) { case
'i' : case
's' : itoa((*pi),s,10); strcat (line,
s); pi++; break ; case
'c' : { int
len = strlen (line); line[len]
= ( char )(*pi); line[len+1]
= '' ; } break ; case
'l' : ltoa((*( long
*)pi),s,10); strcat (line,
s); pi++; break ; default : break ; } } } |
1
2
3
4
5
6
7
8
9
10
11
|
int
Max( int
n, ...) { int
*p = &n + 1; int
ret = *p; for
( int
i=0; i<n; i++) { if
(ret < *(p + i)) ret
= *(p + i); } return
ret; } |
1
2
3
4
5
|
int
m = Max(3, 45, 12, 56); int
m = Max(1, 3); int
m = Max(2, 23, 45); int
first = 34, second = 45, third=5; int
m = Max(5, first, second, third, 100, 4); |
对于可变参数函数的调用有一点需要注意,实际的可变参数的个数必须比前面模式指定的个数要多,或者不小于, 也即后续参数多一点不要紧,但不能少, 如果少了则会访问到函数参数以外的堆栈区域,这可能会把程序搞崩掉。前面模式的类型和后面实际参数的类型不匹配也有可能造成把程序搞崩溃,只要模式指定的数据长度大于后续参数长度,则这种情况就会发生。如:
1
|
printf ( "%.3f,
%.3f, %.6e" ,
1, 2, 3, 4); |