共享转贴【一同事的c++学习心得笔记】之三【可变参数探析】
可变参数探析
在c/c++中函数可以有可变参数,最出名的自然是大名鼎鼎的printf.
int printf(__in_z __format_string const char * _Format, ...); //摘自VC8.0 C library head file
其中的...即是c/c++中的可变参数的语法.
而可变参数是用如下四个宏(Macro)实现的.
va_list
va_start
va_arg
va_end
具体怎么用这四个宏来写带可变参数的函数,请参考任何一本 c 教材.
现摘录C语言开山之作 --- C Language Programming中的一个例子.
#include <stdarg.h>
#include <stdio.h>
void minprintf(char *fmt, ...)
{
va_list ap;
char *p, *sval;
int ival;
double dval;
va_start(ap, fmt);
for(p = fmt; *p; p++)
{
if(*p != '%')
{
putchar(*p);
continue;
}
switch(*++p)
{
case 'd':
ival = va_arg(ap, int);
printf("%d", ival);
break;
case 'f':
dval = va_arg(ap, double);
printf("%f", dval);
break;
case 's':
for(sval = va_arg(ap, char*); *sval; sval++)
{
putchar(*sval);
}
break;
default:
putchar(*p);
break;
}
}
va_end(ap);
}
在VC8.0下添加下面的调用并编译.
int main()
{
minprintf("%s - %d - %f - %d\n", "aaa", 10, 2.34, 3);
return 0;
}
output:
F:\var_len_arg\Debug>var_len_arg.exe
aaa - 10 - 2.340000 - 3
咱们看一下这四个宏的定义.
typedef char * va_list;
va_list 只是 char * 的代名词.
#define va_start _crt_va_start
#define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
#define _ADDRESSOF(v) ( &reinterpret_cast<const char &>(v) )
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
代换以下va_start(ap, v) ( ap = ( ( (char *)&reinterpret_cast<const char &>(v) ) + \
( (sizeof(v) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) ) )
v是某个参数,ap = v参数的地址 + v参数的类型的大小(sizeof(v)) ,并对整数地址取整
罗索了这么多,这不就是使ap指向 v 参数的下一个参数的地址吗?
举个例子
va_start(ap, fmt);
fmt是char *, 是指针,即sizeof(char*) = 4
ap = (&fmt + 4 + 3) & 0xFFFFFFFC0;
stack
| |
| ... |
|___________|<--- ap = (&fmt + 4 + 3) & 0xFFFFFFFC0;
| |
| fmt |
|___________|<------- &fmt
| |
| |
#define va_arg _crt_va_arg
#define _crt_va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
代换以下 va_arg ( *(t *)((ap += ( (sizeof(t) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) \
( (sizeof(t) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) )
举个例子
ival = va_arg(ap, int);
ival = (*(int *)(((ap + sizeof(int)) & 0xFFFFFFFC0) - ((sizeof(int) + 3) & 0xFFFFFFFC0));
一句话,把ap所指向的4 bytes区域解释成int type,并累增ap,使它指向下一个参数地址, 即ival = *ap++;
stack
| |
| 2.34 |
|___________| <--- new ap (va_arg(ap, int)以后)
| |
| 10 | 10 是 int type, 正好占 4 个bytes 空间
|___________| <--- ap = (&fmt + 4 + 3) & 0xFFFFFFFC0;
| |
| fmt |
|___________|<------- &fmt
| |
| |
那么为什么总有烦人的_INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )呢?
两个原因:
1. 每种类型所占空间是不一样的,比如
sizeof(int) = 4
sizeof(double) = 8
sizeof(char *) = 4
sizeof(char) = 1
sizeof(short) = 2
等等
(sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1)
------
这就是上面画下划线的部分的作用
2. 每个参数的地址必须对齐在 4 字节边界
(sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1)
-------------------
这就是上面画下划线的部分的作用
#define va_end _crt_va_end
#define _crt_va_end(ap) ( ap = (va_list)0 )
等于空,完全可以不需要,估计只是为了与va_start匹配.(最起码在这里VC8.0 C Library中是这样.)
minprintf("%s - %d - %f - %d\n", "aaa", 10, 2.34, 3);
调用的stack如下:
stack
| |
| 3 |
|___________| <--- new ap (va_arg(ap, double)以后)
| |
| |
| | sizeof(double) = 8
| |
| 2.34 |
|___________| <--- new ap (va_arg(ap, int)以后)
| |
| 10 | 10 是 int type, 正好占 4 个bytes 空间
|___________| ap = (&fmt + 4 + 3) & 0xFFFFFFFC0;
| | va_list ap;
| fmt |
|___________| <------- &fmt
| |
| |
上面的解释应该够明白了吧?
如果还有点糊涂,那没办法,只能看 cpu 怎么执行的了!