【C++】变参函数va_start,va_arg,va_end介绍及实现方式

如果写过JS的话,就知道在JS中定义一个函数,就算输入的实参和形参不一致,也可以同过arguments获取参数

function abc(x)
{
	console.log(x)
    console.log(arguments[0])
    console.log(arguments[1])
    console.log(arguments[2])
}
abc(1,2,3)

上例输出

1
1
2
3

在C/C++中其实也可以通过一些方式,获取实参中没有定义的多余参数,被称作变参函数

以下代码在32位 vs2015 win10中调试通过

变参函数

在C/C++定义一个变参函数需要使用三个点…,定义形参

void abc(...)

然后实参的话,就可以随便传入任何类型和数量的参数

abc(3,4,"aaa");

要在被调用的函数中取得参数值的话,在C/C++中有专们的函数来处理接收实参,一般情况下需要三个宏和一个类型来配合使用

基本使用如下

void abc(int i,...)
{
    va_list argptr;
    va_start(argptr, i);
    int aaa = va_arg(argptr, int);
    char * bbb = va_arg(argptr, char*);
    va_end(argptr);
    printf("i:%d,aaa:%d,bbb:%s",i,aaa,bbb);
}

int main() {
    
    abc(3,4,"aaa");
    return 1;
}

以上代码输出

i:3,aaa:4,bbb:aaa

宏定义说明如下:

va_list:定义了使用va_start,va_arg,va_end所需要的一些信息
va_start:表示的是设置头一个参数,它的第一个参数是va_list 当以的变量,第二个参数是函数形参的变量名
va_arg:表示从第二个参数开始获取实参,它的第一个参数是va_list定义的变量,第二个参数是函数第二个参数的类型名称,每调用一次va_arg,就获取下一个参数
va_end:表示清理va_list定义

使用时的整个流程如下

1.定义va_list
2.将第一步定义的va_list,传入va_start,获取函数参数的第一个
3.反复调用va_arg,获取变参函数未定义在形参内的变量
4.用va_end清理va_list

整个使用方式流程并不复杂

变参函数的实现原理

实际上实现上述操作,并不复杂,只要知道函数是如何调用的,参数是如何传入的,如何被存储的就可以。

调用一个函数,如下例

void abc(int a,int b)
{
	printf("%d,%d", a, b);
}
int main() {	
	abc(1, 2);
	return 1;
}

执行这个函数时,首页要从最后一个参数2,开始把参数都压入栈内,然后执行call调用abc

push 2  
push 1  
call abc
add  esp,8 

在执行到abc内以后,存储在栈内参数如下
在这里插入图片描述
可以看到参数都是排列在一起的,所以只要知道第一个参数地址,后面的值,按照他所占用空间大小就可以拿出来

自己实现va_start,va_arg,va_end

按上述描述,事实上参数都是挨着的,只要知道第一个参数的地址,后面就都能知道,所以可以模仿也写一个

代码如下

#define my_va_start(a,b) a =(char*)&b
#define my_va_arg(a,b)  *((b*)(a = a + sizeof(b)))
#define my_va_end(a) a = 0
void abc(int i, ...)
{

	char* my_wa_list;
	my_va_start(my_wa_list, i);
	int aaa = my_va_arg(my_wa_list, int);
	char * bbb = my_va_arg(my_wa_list, char*);
	my_va_end(my_wa_list);
	printf("i:%d,aaa:%d,bbb:%s", i, aaa, bbb);
}
int main() {
    
    abc(3,4,"aaa");
    return 1;
}

以上代码输出

i:3,aaa:4,bbb:aaa

用更简单的方法实现

在一个函数被call的时候,实际上,参数存在的位置前两已经存在两个值
在这里插入图片描述
第一个框:记录了在调用函数时,调用函数EBP的内容
第二个框:记录了被调用函数执行完毕后,要返回的代码地址,也就是执行完call abc后要执行的下一句地址

然后,后面就是参数了

所以取值时候,只需要取到EBP+8以后的两个值,就是参数的两个值,所以,函数即使没有形参,也可以取值

知道取值方式,实际上都不需要函数的第一个参数的地址,就可以取出,下面用内嵌asm实现

void abc(...)
{	
	int a, b;
	char* c;
	_asm {
		mov eax,[ebp+8]
		mov a,eax
		mov eax,[ebp+12]
		mov b,eax
		mov eax,[ebp+16]
		mov c,eax
	}	
	printf("a:%d,b:%d,c:%s", a, b, c);
}

int main() {
	abc(3, 4, "aaa");
	return 1;
}

以上代码输出

a:3,b:4,c:aaa
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值