基于C中变参的认识

今天在工作之余,突然有个朋友说到由于工作需要,需要用到C的变参。这个呢,以前大学里面研究过,实习的时候也用到过,但是没有怎么深入,这次我决定深入的去了解一下C的变参是怎么实现的。

首先看了printf的源代码。下面将以printf为例子来说明。毕竟我们最先接触的函数里面就有这个函数。

先贴实现代码,看了再说。(这里我只解析 几种变量,其他的解析是一样的。)

#define  MY_INSIZEOF(n) ((sizeof(n) + sizeof(int) - 1)& ~(sizeof(int) - 1))
#define  my_va_list char *
#define  my_va_start(v, va) v = (my_va_list)&va + MY_INSIZEOF(va);
#define  my_va_end(v) v = 0
#define  my_va_arg(v, vsize) *(vsize *)((v += MY_INSIZEOF(vsize)) - MY_INSIZEOF(vsize))
void my_printf(char *format, ...)
{
 //va_list
 my_va_list list;
 my_va_start(list, format);

 while(*format != NULL)
 {
  if (*format == '%')
  {
   if (*(format + 1) == 'c')
   {
    char c = my_va_arg(list, char);
    putc(c, stdout);
   }
   else if(*(format + 1) == 's')
   {
    char* pStr = my_va_arg(list, char*);
    puts(pStr);
   }
   else if(*(format + 1) == 'd')
   {
    int nVal = my_va_arg(list, int);
    char buf[12];
    memset(buf, 0, sizeof(buf));
    itoa(nVal, buf, 10);
    puts(buf);
   }
   else if(*(format + 1) == 'f')
   {
    double fVal;
    fVal = my_va_arg(list, double);//这里不能直接写float型,后面有介绍
    char buf[22];
    memset(buf, 0, sizeof(buf));
    gcvt((float)fVal, 10, buf);
    puts(buf);
   }
    else
    {
    putc(*format, stdout);
    --format;
   }
   format++;
  }
  else putc(*format, stdout);
  format++;
 }
 my_va_end(list);
}

 

都知道函数调用的时候,参数传递的方式主要有2种,一种是通过将参数压入堆栈的形式,一种是通过放入寄存器,但最常用的还是将参数压入堆栈。而基于_stdcall调用,参数压栈方式是从右到坐的方式。即各个参数的地址是紧挨着的,通过地址的操作可以访问到下一个参数的地址。上面是通过操作指针来实现访问参数,后面还有一种通过汇编来实现参数解析

my_va_start的作用是取第一个参数的下一个参数的地址

由于不同的参数类型,所占的内存空间大小不一样,那么也就造成了在访问内存时应该向后移几位的问题,而且由于系统为了访问快速,做参数做了字节对齐,这里的MY_INSIZEOF即是根据参数的大小做内存对齐,不然可能访问的地址并不是真正的参数的地址,或许错位了。

my_va_list 此宏主要是用于操作内存单元,我们都知道,每一个字节为8位,而刚好每一个char也是8位,那么这里也就是为了让在访问对应内存时能一字节一字节的访问。

my_va_arg这个宏很简单,由于我们在函数开始执行时,list已经指向了函数的第二个参数。这个宏的大致意思就是,将参数强制转换为我们需要的类型,并将list指向下一个参数的地址(可以理解为汇编中的esp)。

上面的是通过C的方式实现,看起来有点难懂,特别是宏,下面以汇编的形式来解析参数,很好明白。(先看下面的汇编,再去看上面的C代码方式,将会简单多了)(这里以输出一个字符为例,输出其他参数是一样,只是我们在读出内存中的值的同时,根据我们需要的参数解析即可)

void my_printf2(char *format, ...)
{
 while(*format != NULL)
 {
  if (*format == '%')
  {
   if (*(format + 1) == 'c')
   {
    char dst;
    _asm
    {
     push eax ;保存eax
     push ebx ;保存ebx
     push ecx ;保存ecx
     mov eax, esp ;将esp的值放入eax中
     lea ebx,format;将format的地址读入ebx (即在栈中的位置)
     mov esp, ebx;将esp指向format在栈中的值,即指向第一个参数的位置
     pop ebx;弹出第一个参数;这个参数即是format
     pop ecx;弹出第二个参数,即这里是我们需要的参数
     mov dst, cl;由于一个字符只占用1个字节,存放在低地址cl中,将cl中的字符赋值到dst中
     ;push ecx;这两句可要可不要,因为后面直接修改了esp指针,为了可读性可以加上,
     ;push ebx
     mov esp,eax;后面的都是还原环境
     pop ecx
     pop ebx
     pop eax
    }
    putc(dst, stdout);
   }
   else
   {
    putc(*format, stdout);
    --format;
   }
   ++format;
  }
  ++format;
 }
}

大部分代码和上面的都差不多,而且这个代码看着就少多了。上面汇编有注释,这里也就不多解释了,我感觉用汇编来写,代码又简洁又易懂。简单多了。

上面还有值得注意的便是第一种方法在解析float型的时候,不能写float型,因为系统在传参数的时候,直接传的double型,即我们要按照double型来解析,不然就解析的数据不对了,这里我就不贴图了,如果想自己证明的话,很简单,直接看内存的值即可。还有,在传递浮点数的时候,如果是直接写的参数的话,必须强制转为为float或者double才能正确输出,例如;printf("%f",123),那么这样打印出来的值其实是不对的,原因就是当你直接写123的时候,系统并不知道你想以float的形式保存,那么系统将会自动以整形的形式存储在内存中,但是我们在解析的时候,确是以浮点数的形式去解析,那么将导致解析的值不对。所以在这种情况下,要么我们可以定义一个浮点数的变量,并在传参的时候传递这个变量过去,要么就是强制转换,即

printf(“%s”,(float)123),这种的话,输出结果是正确的。

浮点数的存储方式并不像整形那样,网上的介绍很多,不懂的可以去看看,如果以后有机会,我会将其记录下来。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值