C语言如何编写可变参数函数(涉及到二级指针)

来源:互联网 发布:数据接口功能 编辑:程序博客网 时间:2024/04/30 19:04

C语言中函数支持可变参数,常见的可变参数的函数有printf()函数,他的函数原型是_CRTIMP int __cdecl printf(const char *, ...);

可变函数的核心就在,...这里。


既然printf()函数是可变函数,这里就简单模拟printf()函数的功能写个myprintf()函数

#include <iostream>using namespace std;//************************************// Method:    myprintf// FullName:  myprintf// Access:    public // Returns:   int// Qualifier:// Parameter: const char * fmt// Parameter: ...//************************************int myprintf(const char *fmt,...){int i = 0;//记录传入数组fmt的下表//int index = 0;//输出字符串下标int arg = -1;//参数个数//char putfmt[1024];//输出字符串char *s;//临时存储参数int ii=0;bool status = true;//正确标志//memset(putfmt,0,1024);int length = strlen(fmt)+1;for(i=0;i<length;i++){if(fmt[i]=='%'){i++;int flag = 0;int *ppp;switch(fmt[i]){case 's':if(-1 == arg)arg = 0;flag = 1;ppp=(int *)&fmt+(1+arg);s = (char *)*ppp;ii = 0;while(s[ii])cout<<s[ii++];//putfmt[index++]=s[ii++];arg++;break;case 'd':flag = 1;ppp=(int *)&fmt+(1+arg);cout<<*ppp;arg++;break;default:break;}if(0 == flag){status = false;break;} }elsecout<<fmt[i];//putfmt[index++]=fmt[i];}return arg;}void main(void){myprintf("hello %s","oba没有马!");}


运行结果(图1):


    图1

大致的原理:

第一个形参起重要作用,用来判断后面有几个参数,其中可以用%d来识别后面有个int类型变量,%s识别后面有个字符串变量,以此类推。取的第一个参数后,可通过指针偏移来获取可变参数的地址,继而进行后续的操作。

以本文章中的例子为例,可通过调试的形式来更形象的解释(图2)。


         图2


其中第一个固定参数的地址为0x0043201c,通过查看内存地址可以看到对应的内容。而第二个参数可变参数紧跟在第一个参数对应的内存地址的后面(内存中并非显示正确的字是由于中文为两个字节,oba有三个字节,后面三个字的编码被错开放了,因此显示不一样的字)。那么,要如何正确的在内存中识别中相应的参数呢?

就上面的代码来说,函数中传递了两个参数,都是字符串类型,而字符串类型在传递给函数的过程中传递的是首地址,也可以理解为指针。既然是指针,而且同为一个函数的参数,那在指针的地址(二级指针)上会不会有所关联?

通过&fmt查看其地址(图3)


         图3


由图3可知,&fmt的地址为0x0019feec,在内存中追踪该地址,可看到在内存中,0x0019feec-0x0019feef对应的值为1C 20 43 00,由于在INTEL CPU中,数据的存储形式为小端模式(低地址存地位,不懂可以看我的另一篇博客大端模式,小端模式的区别),因此&fmt里面的值为0x43201C,该值就是fmt的地址。而&fmt后面跟的四个字节,跟&fmt的值有点相像,取其值0x432028查看对应的地址(图4):



    图4


看到了没有!?这个地址里面的存的值就是第二个参数的的内容!

理一下思路:在调用自定义可变参数函数myprintf("hello %s","oba没有马!");时,总共传递了两个参数,一个固定参数"hello %s",另一个传递了可变参数"oba没有马",这两个参数(一级指针)的内容是非连续的(图1,中间有4个字节的00隔开,后面再讨论),但是它们的地址(二级指针)是连续的(图3)。因此可以通过二级指针的偏移来定义可变参数。

在函数myprintf()中,第一个固定参数fmt的类型为char *,取其地址后为0x0019feec,需要偏移4个字节到下一个地址。偏移到0x0019fef0时,此时为二级指针,需进行降级操作,用取值符*降成一级指针,此时指向地址0x00432028,再取里面的值就是要的可变参数的值了。


前面研究的为一个可变参数,如果更多个呢?那在内存中的形式又是怎么样的?下面用三个可变参数作为测试:

修改后的main函数

void main(void){myprintf("hello %s %s %s","oba没有马!","5","you");}
调试看其内存(图5)


图5


由图5可以看出,参数所占的内存大小以4字节对齐,不足4个字节的补全。参数跟参数之间有4个空白字节作为隔断(图5中的红色框部分)。再看它们的地址(二级指针,图6)


图6

图6中,红框部分为四个参数的二级指针!有兴趣的读者可将四个参数对应的指针代入图5中进行验证。


二级指针:

比如说一个字符串指针,char *arr=“hello",其中"hello"存在一个内存空间中,占5个字节,而指针只能存四个字节,因此要用一个指针表示5个地址的字符串,只能通过地址的形式表示,arr就是一级指针,指向"hello"的内存地址的首地址。现在问题来了,形参表中如何才能做到又能保存字符串,又能保存Int,float等其他类型的变量呢?

答案就是用二级指针(图7)。


把main函数中的代码改成:

void main(void){myprintf("hello %s %d %s","world",5,"you");}
调试时结果如图7所示。

注:本文章的重点在于可变参数函数的研究,不在于printf()的研究,因此printf()只是简单的实现!


0 0