cdecl调用规范和stdcall调用规范都支持变长参数,由于这两种规范都是通过堆栈传递入口参数,并且入栈顺序都是由右至左,这保证了函数的第一个入口参数离堆栈中的返回地址最近,这样可以很方便的通过堆栈帧基指针ebp来访问,如下图所示:
![堆栈示意图](https://p-blog.csdn.net/images/p_blog_csdn_net/denlee/EntryImages/20090604/stack_2009_0604.jpg) 图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
当然,对于变长参数的个数以及类型的获取可能有更很好的方法,但是这里给出的方法只是一种参考。
|
|