欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

可变参函数(my_printf可变参函数的实现)

程序员文章站 2022-06-03 13:12:32
...

可变参函数:
其参数列表的参数类型与个数可变,采用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)  

可变参函数(my_printf可变参函数的实现)

C 语言中可变长参数的处理心得

#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;
}
相关标签: 函数 printf