可变参函数(my_printf可变参函数的实现)
可变参函数:
其参数列表的参数类型与个数可变,采用ANSI标准形式时,参数个数可变的函数的原型声明是:
type funcname(type para1, type para2, ...)
//至少需要一个普通的形式参数,后面的省略号不表示省略,而是函数原型的一部分,为参数占位符,type是函数返回值和形式参数的类型
可变参函数的实现:
1、采用ANSI标准形式
2、可变参数列表:
通过定义在stdarg.h头文件(标准库的一部分)的宏来实现。
1)一个类型: va_list
typedef char * va_list;
//用来保存宏va_start、va_arg和va_end所需信息的一种类型;
//为了访问变长参数列表中的参数,必须声明 。
2)三个宏:仅用来确定可变参数列表中每个参数的内存地址
(1)va_start
#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) )
//相当于 &v
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
//访问变长参数列表中的参数之前使用的宏;
//初始化用va_list声明的对象,初始化结果供宏va_arg和 va_end使用
(2)va_arg
#define va_arg _crt_va_arg
#define _crt_va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
//展开成一个表达式的宏;
//该表达式具有变长参数列表中下一个参数的值和类型,每次调用va_arg都会修改
(3)va_end
#define va_end _crt_va_end
#define _crt_va_end(ap) ( ap = (va_list)0 )
//展开成一个表达式的宏;
//该表达式具有变长参数列表中下一个参数的值和类型。每次调用va_arg都会修改
3)可变参数在编译器中的处理:
(1)首先把va_list被定义成char*(我们目前所用的PC机上,字符指针类型可以用来存储内存单元地址),有的机器上va_list是被定义成void*。
(2)定义_INTSIZEOF(n)主要是为了某些需要内存的对齐的系统,为了得到最后一个固定参数的实际内存大小(可直接用sizeof运算符来代替)。
(3)va_start的定义为 &v+_INTSIZEOF(v) ,这里&v是最后一个固定参数的起始地址,再加上其实际占用大小后,目的为了得到第一个可变参数的起始内存地址;
当运行va_start(ap, v)后,此时ap指向第一个可变参数在的内存地址。
(4)va_arg由运行va_start(ap, v)后,取得第一个可变参数的地址,ap访问该地址根据指定的参数类型取得本参数的值,并且把指针调到下一个参数的起始地址。
①用用户输入的类型名对参数地址进行强制类型转换,得到用户所需要的值;
②计算出本参数的实际大小后将指针调到本参数的结尾,即下一个参数的首地址,便于处理下一个参数。
(5)va_end 的定义相当于((void*)0),使ap不再 指向堆栈,而是置NULL。
3、重要因数:
①函数栈的生长方向:向下生长,栈顶指针的内存地址低于栈底指针,所以先进栈的数据是存放在内存的高地址处,从栈底向栈顶看过去,地址是从高地址走向低地址的,因为称它为向下生长
(栈由编译器自动管理,其中存放的是函数参数及局部变量)
②参数的入栈顺序:自右向左,并且连续存储
参数进栈内存模型如下:
内存地址 内容
高内存地址处 最后一个可变参数 –栈顶出
……
第N个可变参数 –va_arg(arg_ptr,int),调用第N个可变参数的地址
……
第一个可变参数 –va_start(arg_ptr, start),得第一个可变参数地址
最后一个固定参数 –start的起始地址
……
低内存地址处 第一个固定参数
注意:最后一个固定变量不能声明为寄存器类型变量,函数类型变量或数组类型变量
③CPU的对齐方式
④内存地址的表达方式
4、举例说明:printf函数
//原型:printf(const char * _Format, ...);
int main()
{
char a = 'a';
int b = 2;
float c = 3.1;
char *str = "abc";
printf("%c %d %f %s\n", a, b, c, str);
getchar();
return 0;
}
//反汇编
char a = 'a';
00E113EE mov byte ptr [a],61h
int b = 2;
00E113F2 mov dword ptr [b],2
float c = 3.1;
00E113F9 movss xmm0,dword ptr ds:[0E1586Ch]
00E11401 movss dword ptr [c],xmm0
char *str = "abc";
00E11406 mov dword ptr [str],0E15858h
printf("%c %d %f %s\n", a, b, c, str);
00E1140D mov esi,esp
00E1140F mov eax,dword ptr [str]
00E11412 push eax
00E11413 cvtss2sd xmm0,dword ptr [c]
00E11418 sub esp,8
00E1141B movsd mmword ptr [esp],xmm0
00E11420 mov ecx,dword ptr [b]
00E11423 push ecx
00E11424 movsx edx,byte ptr [a]
00E11428 push edx
00E11429 push 0E1585Ch
00E1142E call dword ptr ds:[0E192C0h]
00E11434 add esp,18h
00E11437 cmp esi,esp
00E11439 call __RTC_CheckEsp (0E11136h)
#include<stdio.h>
#include<stdarg.h>
#include<string.h>
//va_start(ap, v)后,此时ap指向第一个可变参数在的内存地址
//va_arg(ap,t), ap访问该地址根据指定的参数类型取得本参数的值,并且把指针调到下一个参数的起始地址
//va_end(ap), ap不再 指向堆栈,而是置NULL
//打印%d
void print_int(const int num)
{
if(num)
{
print_int(num / 10);
putchar((char)(num % 10 + '0'));
}//itoa逆序输出(先递归,后输出实现)
else
{
return;
}
}
//打印%c
void print_char(const char ch)
{
putchar(ch);
}
//打印%s
void print_str(const char *str)
{
while(*str != '\0')
{
putchar(*str);
str++;
}
}
//打印%f
void print_float(const float f)
{
if(f)
{
int temp1 = (int)f;
int temp2 = (int)(1000000 * (f - temp1));
print_int(temp1);
putchar('.');
print_int(temp2);
}
else
{
return;
}
}
void my_printf(const char *format, ...)
{
va_list ap;
va_start(ap, format);//将ap指向第一个实际参数的地址
while(*format)//遍历第一个固定参数字符串内容
{
if(*format != '%')
{
putchar(*format);
format++;
}
else
{
format++;
switch(*format)//选择判断输出格式
{
case 'd':
{
int buffer = va_arg(ap, int);//得到此时指向的实际参数地址,然后ap再指向下一个实际参数
print_int(buffer);
format++;
break;
}
case 'c':
{
char buffer = va_arg(ap, char);
print_char(buffer);
format++;
break;
}
case 's':
{
char *buffer = va_arg(ap, char *);
print_str(buffer);
format++;
break;
}
case 'f':
{
float buffer = va_arg(ap, double);
print_float(buffer);
format++;
break;
}
default:
{
print_char(*format);
format++;
}//原样输出语句
}
}
}
va_end(ap);
}
int main()
{
int num = 123;
char a = 'a';
char *str = "hello";
float f = 12.125;
my_printf("%d %c %s %f\n", num, a, str, f);
getchar();
return 0;
}
上一篇: 第一个hello word
下一篇: JavaWeb——【前端】——注册页面