变长参数思考[原]

变长参数思考[原]
Denlee, 2009年6月4日

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

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

;变长参数子程序示例
;Denlee, 2009/06/03
.386
.model flat, c
option casemap:none

includelib msvcrt.lib

printf	proto c, :ptr byte, :vararg
scanf	proto c, :ptr byte, :vararg
exit	proto c, :dword
public main

.data
	x	dd 1000
	y	dd -9807
	z	dd 8976
	sum	dd ?
	sintip	db "please input integer for x,y,z", 0ah, 0
	stip	db "the sum of x+y+z = %d", 0ah, 0
	sindc	db "%d%d%d", 0
.code
main:
	invoke printf, addr sintip
	invoke scanf, addr sindc, addr x, addr y, addr z
	push z
	push y
	push x
	push 3
	call qsum1
	add esp, 16
	mov sum, eax
	invoke printf, addr stip, sum
	invoke exit, 0	;结束程序

;result in eax
;求若干整型变量的和
qsum1 proc
	;建立堆栈帧
	push ebp
	mov ebp, esp
	;局部变量空间
	sub esp, 16
	push ebx
	;取参数个数
	mov ecx, [ebp+8]
	;第二个参数(变长参数的第一个)在栈中的地址
	lea ebx, [ebp+12]
	mov eax, 0
next1:
	add eax, ss:[ebx]
	add ebx, 4
	loop next1
	pop ebx
	mov esp, ebp
	pop ebp
	ret
qsum1 endp
end 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 called
DemoFunc("#d",1) is called
1's argument is :1
DemoFunc("#f#d", 1.0, 2) is called
1's argument is :1.000000
2's argument is :2
DemoFunc("#d#d#f#d#f", 10, 20, 10.12, 30, 30.30) is called
1's argument is :10
2's argument is :20
3's argument is :10.120000
4's argument is :30
5's argument is :30.300000
DemoFunc("#d#s#f", 50, "this is a example", 10.09) is called
1's argument is :50
2's argument is :this is a example
3's argument is :10.090000
Press 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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值