#include <stdio.h>linux
#include <stdarg.h>windows
void myprintf(constchar *format, ...)数组
{函数
va_list ap;post
char c;spa
va_start(ap, format);操作系统
while ((c = *format++))指针
{orm
switch(c)对象
{
case 'c':
{
char ch = va_arg(ap, int);
putchar(ch);
break;
}
case 's':
{
char *p = va_arg(ap, char *);
fputs(p, stdout);
break;
}
default:
putchar(c);
break;
}
}
va_end(ap);
}
int main(void)
{
myprintf("s ccc\n","hello",'b','i','t');
return 0;
}
这里要知道两个事情:
⑴在intel+windows的机器上。函数栈的方向是向下的,栈顶指针的内存地址低于栈底指针,因此先进栈的数据是存放在内存的高地址处。
(2)在VC等绝大多数C编译器中,默认状况下,參数进栈的顺序是由右向左的,所以,參数进栈之后的内存模型例如如下图所看到的:最后一个固定參数的地址位于第一个可变參数之下,并且是连续存储的。
|--------------------------|
| 最后一个可变參数 | ->高内存地址处
|--------------------------|
|--------------------------|
| 第N个可变參数 | ->va_arg(arg_ptr,int)后arg_ptr所指的地方,
| | 即第N个可变參数的地址。
|--------------- |
|--------------------------|
| 第一个可变參数 | ->va_start(arg_ptr,start)后arg_ptr所指的地方
| | 即第一个可变參数的地址
|--------------- |
|------------------------ --|
| |
| 最后一个固定參数 | -> start的起始地址
|-------------- -| .................
|-------------------------- |
| |
|--------------- | -> 低内存地址处
(4) va_arg():有了va_start的良好基础,咱们取得了第一个可变參数的地址。在va_arg()里的任务就是依据指定的參数类型取得本參数的值,并且把指针调到下一个參数的起始地址。
所以。现在再来看va_arg()的实现就应该心中有数了:
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
这个宏作了两个事情,
①用用户输入的类型名对參数地址进行强制类型转换,获得用户所需要的值
②计算出本參数的实际大小,将指针调到本參数的结尾。也就是下一个參数的首地址,以便兴许处理。
(5)va_end宏的解释:x86平台定义为ap=(char*)0;使ap再也不 指向堆栈,而是跟NULL同样.有些直接定义为((void*)0),这样编译器不会为va_end产生代码,好比gcc在linux的x86平台就是这样定义的. 在这里你们要注意一个问题:由于參数的地址用于va_start宏,因此參数不能声明为寄存器变量或做为函数或数组类型. 关于va_start, va_arg, va_end的描写叙述就是这些了,咱们要注意的 是不一样的操做系统和硬件平台的定义有些不一样,但原理倒是类似的.