变长参数思考[原]

来源:互联网 发布:集成招标大数据 编辑:程序博客网 时间:2024/05/05 20:17
变长参数思考[原]
Denlee, 2009年6月4日

  cdecl调用规范和stdcall调用规范都支持变长参数,由于这两种规范都是通过堆栈传递入口参数,并且入栈顺序都是由右至左,这保证了函数的第一个入口参数离堆栈中的返回地址最近,这样可以很方便的通过堆栈帧基指针ebp来访问,如下图所示:
堆栈示意图
图1 函数堆栈示意图

  但是变长参数有2个问题,一个是参数个数的问题,另一个是参数类型的问题。对于参数个数的问题,大多是采用通过传递一个表明参数个数的参数来解决,下面通过汇编语言程序来给出这种方式,并且说明如何建立堆栈帧,如何通过堆栈帧基指针来访问堆栈中的参数的问题。程序如下:

;变长参数子程序示例;Denlee, 2009/06/03.386.model flat, coption casemap:noneincludelib msvcrt.libprintfproto c, :ptr byte, :varargscanfproto c, :ptr byte, :varargexitproto c, :dwordpublic main.dataxdd 1000ydd -9807zdd 8976sumdd ?sintipdb "please input integer for x,y,z", 0ah, 0stipdb "the sum of x+y+z = %d", 0ah, 0sindcdb "%d%d%d", 0.codemain:invoke printf, addr sintipinvoke scanf, addr sindc, addr x, addr y, addr zpush zpush ypush xpush 3call qsum1add esp, 16mov sum, eaxinvoke printf, addr stip, suminvoke exit, 0;结束程序;result in eax;求若干整型变量的和qsum1 proc;建立堆栈帧push ebpmov ebp, esp;局部变量空间sub esp, 16push ebx;取参数个数mov ecx, [ebp+8];第二个参数(变长参数的第一个)在栈中的地址lea ebx, [ebp+12]mov eax, 0next1:add eax, ss:[ebx]add ebx, 4loop next1pop ebxmov esp, ebppop ebpretqsum1 endpend main

  这种方法的一个特点就是要通过传递参数明确指明参数的个数,并且事先约定每个参数的类型,不能充分发挥变长参数的优势。

  在C语言中,有几个宏专门用于变长参数,在Microsoft Visual Studio/VC98/Include/stdarg.h中的定义如下所示:

typedef char *  va_list;#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )#define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )#define va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )#define va_end(ap)      ( ap = (va_list)0 )

  关于关于这几个宏的实现得机理在这里不做深入探讨,只是简单描述一下使用方法。关于这几个宏的深入分析查阅参考资料1,2和3。

(1)首先在函数里定义一个va_list型的变量,这个变量是指向参数的指针。如va_list ap;(2)然后用va_start宏初始化变量ap,使ap指向第一个可变参数,这个宏的第二个参数是第一个可变参数的前一个参数,是一个固定的参数。如va_start(ap, frs_v);(3)然后用va_arg返回可变的参数,va_arg的第二个参数是你要返回的参数的类型,实型都按double。如:va_arg(ap, double);(4)最后用va_end宏结束可变参数的获取,如:va_end(ap);然后就可以在函数里使用所获得的参数。如果函数有多个可变参数的,依次调用va_arg获取各个参数。

  为了能够既获得变长参数的个数又能获得变长参数的类型,可以考虑采用类似于库函数中printf和scanf函数的指示字符串的方法,将指示字符串作为第一个固定参数,然后在函数内部通过解析指示字符串来获取参数的个数以及每个参数的类型,在解析字符串时可以借助队列来实现。下面给出实现的代码(未给出队列的实现):

/**变长参数示例*通过队列分析参数指示串*Denlee, 2009/06/04  */#ifndef _INC_LINKQUEUE#include "LinkQueue.h"#endif#ifndef _INC_STDARG#include #endif#ifndef _INC_STDIO#include #endif//类型引导标识符char header = '#';void DemoFunc(char *sind, ...){int i = 0, num;va_list ap;char ch;void *pdata;pLinkQueue pqueue;//构造空队列InitQueue(&pqueue);while(sind[i]){    //跳过空格while(sind[i] == ' ')i++;if(sind[i++] != header)break;        //2个连续的#为结束或无变长参数if(sind[i] == '#')break;EnQueue(pqueue, sind[i]);i++;}va_start(ap, sind);num = QueueLength(pqueue);for(i = 0; i < num; i++){DeQueue(pqueue, &ch);switch(ch){case 'd':pdata = (void *)&va_arg(ap, int);printf("%d's argument is :%d/n", i+1, *((int *)pdata));break;case 'f':pdata = (void *)&va_arg(ap, double);printf("%d's argument is :%f/n", i+1, *((double *)pdata));break;case 's':pdata = (void *)va_arg(ap, char*);printf("%d's argument is :%s/n", i+1, (char *)pdata);break;}}va_end(ap);DestroyQueue(pqueue);}int main(void){printf("DemoFunc(/"##/") is called/n");DemoFunc("##");printf("DemoFunc(/"#d/",1) is called/n");DemoFunc("#d",1);printf("DemoFunc(/"#f#d/", 1.0, 2) is called/n");DemoFunc("#f#d", 1.0, 2);printf("DemoFunc(/"#d#d#f#d#f/", 10, 20, 10.12, 30, 30.30) is called/n");DemoFunc("#d#d#f#d#f", 10, 20, 10.12, 30, 30.30);printf("DemoFunc(/"#d#s#f/", 50, /"this is a example/", 10.09) is called/n");DemoFunc("#d#s#f", 50, "this is a example", 10.09);}

程序的运行结果为:
DemoFunc("##") is calledDemoFunc("#d",1) is called1's argument is :1DemoFunc("#f#d", 1.0, 2) is called1's argument is :1.0000002's argument is :2DemoFunc("#d#d#f#d#f", 10, 20, 10.12, 30, 30.30) is called1's argument is :102's argument is :203's argument is :10.1200004's argument is :305's argument is :30.300000DemoFunc("#d#s#f", 50, "this is a example", 10.09) is called1's argument is :502's argument is :this is a example3's argument is :10.090000Press any key to continue

当然,对于变长参数的个数以及类型的获取可能有更很好的方法,但是这里给出的方法只是一种参考。

参考资料:
1. stdarg.h中三个宏va_start ,va_arg和va_end的应用,http://blog.sina.com.cn/s/blog_4e1ac22c0100b93s.html
2. 关于变长参数,http://blog.csdn.net/piao_polar/archive/2007/03/22/1537902.aspx
3. 也谈C语言变长参数, http://bigwhite.blogbus.com/logs/20468193.html
4. 严蔚敏 吴伟民,数据结构(C语言版),清华大学出版社,2008
原创粉丝点击