一、可变参数表介绍
c/c++语言具备一个不同于其他编程语言的的特性,即支持可变参数。
例如C库中的printf,scanf等函数,都支持输入数量不定的参数。例如:
printf("helloworld");////< 1个参数 prinf("%d", a); ////< 2个参数 printf("%d, %d", a, b); ////< 3个参数
printf函数原型为 int printf(const char *format, …);
从printf的原型来看,其除了接受一个固定参数format以外,后面的参数使用…来表示。
在c/c++语言中,…表示可以接受不定数量的参数。
二、可变参数表用法
在标准C/C++中,头文件中定义了如下三个宏:
voidva_start(va_listarg_ptr,prev_param);/*ANSIversion*/ typeva_arg(va_listarg_ptr,type); voidva_end(va_listarg_ptr);
va 就是variable argument(可变参数)的意思
arg_ptr 是指向可变参数表的指针
prev_param 则指可变参数表的前一个固定参数
type 为可变参数的类型
va_list 也是一个宏
其定义为typedef char * va_list 实质上是一char 型指针。
char 型指针的特点是++、--操作对其作用的结果是增1 和减1(因为sizeof(char)为1)。
与之不同的是int 等其它类型指针的++、--操作对其作用的结果是增sizeof(type)或减sizeof(type),而且sizeof(type)大于1。
通过使用va_start宏我们可以取得可变参数表的首指针,这个宏的定义为:
#defineva_start(ap,v)(ap=(va_list)&v+_INTSIZEOF(v))
其作用为将最后那个固定参数的地址加上可变参数对其的偏移后赋值给ap,这样ap就是可变参数表的首地址。
_INTSIZEOF 宏定义为:
#define_INTSIZEOF(n)((sizeof(n)+sizeof(int)–1)&~(sizeof(int)–1))
宏定义va_arg原型为:
#defineva_arg(list,mode)((mode*)(list= (char*)((((int)list+(__builtin_alignof(mode)<=4?3:7)) & (__builtin_alignof(mode)<=4?-4:-8))+sizeof(mode))))[-1]
其作用为指取出当前arg_ptr 所指的可变参数并将ap 指针指向下一可变参数。
va_end宏定义用来结束可变参数的获取,定义为:
#defineva_end(list)
va_end ( list )实际上被定义为空,没有任何真实对应的代码,用于代码对称,与va_start对应;
可能发挥代码的“自注释”作用。所谓代码的“自注释”,指的是代码能自己注释自己。
三、可变参数表的简单使用
#include#include #include /** *@brief求n个数中的最大值 *@details *@param[in]num整数个数 *@param[out]...整数 *@retval最大整数 *@par */ intmax(intnum,...){ intm=-0x7FFFFFFF;/*32系统中最小的整数*/ va_listap; va_start(ap,num); for(inti=0;i< num; i++ ) { int t = va_arg (ap, int); if ( t >m){ m=t; } } va_end(ap); returnm; } intmain(intargc,char*argv[]){ intn=max(5,5,6,3,8,5);/*求5个整数中的最大值*/ cout<< n; return 0; }
max(int num, …)中首先定义了可变参数表指针ap,而后通过va_start ( ap, num )取得了参数表首地址(赋给了ap),其后的for 循环则用来遍历可变参数表。
max函数相比于printf简单了许多,其原因如下:
max函数可变参数表的长度是已知的,通过num参数传入;
max函数可变参数表中参数的类型是已知的,都为int型;
printf 函数可变参数的个数不能轻易的得到,而可变参数的类 型也不是固定的,需由格式字符串进行识别(由%f、%d、%s 等确定)。
四、运行机制
反汇编是研究语法深层特性的终极良策,首先查看main函数中调用max函数时的反汇编:
1.004010C8push5 2.004010CApush8 3.004010CCpush3 4.004010CEpush6 5.004010D0push5 6.004010D2push5 7.004010D4call@ILT+5(max)(0040100a)
第一步:将参数从右向左入栈(第1~6行)
第二步:调用call 指令进行跳转(第7行)
这两步包含了深刻的含义,它说明C/C++默认的调用方式为由调用者管理参数入栈的操作,且入栈的顺序为从右至左,这种调用方式称为_cdecl调用。
x86系统的入栈方向为从高地址到低地址,故第1至n个参数被放在了地址递增的堆栈内。在被调用函数内部,读取这些堆栈的内容就可获得各个参数的值,让我们反汇编到max函数的内部。
intmax(intnum,...){ 1.00401020pushebp 2.00401021movebp,esp 3.00401023subesp,50h 4.00401026pushebx 5.00401027pushesi 6.00401028pushedi 7.00401029leaedi,[ebp-50h] 8.0040102Cmovecx,14h 9.00401031moveax,0CCCCCCCCh 10.00401036repstosdwordptr[edi] va_listap; intm=-0x7FFFFFFF;/*32系统中最小的整数*/ 11.00401038movdwordptr[ebp-8],80000001h va_start(ap,num); 12.0040103Fleaeax,[ebp+0Ch] 13.00401042movdwordptr[ebp-4],eax for(inti=0;i< num; i++ ) 14. 00401045 mov dword ptr [ebp-0Ch],0 15. 0040104C jmp max+37h (00401057) 16. 0040104E mov ecx,dword ptr [ebp-0Ch] 17. 00401051 add ecx,1 18. 00401054 mov dword ptr [ebp-0Ch],ecx 19. 00401057 mov edx,dword ptr [ebp-0Ch] 20. 0040105A cmp edx,dword ptr [ebp+8] 21. 0040105D jge max+61h (00401081) { int t= va_arg (ap, int); 22. 0040105F mov eax,dword ptr [ebp-4] 23. 00401062 add eax,4 24. 00401065 mov dword ptr [ebp-4],eax 25. 00401068 mov ecx,dword ptr [ebp-4] 26. 0040106B mov edx,dword ptr [ecx-4] 27. 0040106E mov dword ptr [t],edx if ( t >m) 28.00401071moveax,dwordptr[t] 29.00401074cmpeax,dwordptr[ebp-8] 30.00401077jlemax+5Fh(0040107f) m=t; 31.00401079movecx,dwordptr[t] 32.0040107Cmovdwordptr[ebp-8],ecx } 33.0040107Fjmpmax+2Eh(0040104e) va_end(ap); 34.00401081movdwordptr[ebp-4],0 returnm; 35.00401088moveax,dwordptr[ebp-8] } 36.0040108Bpopedi 37.0040108Cpopesi 38.0040108Dpopebx 39.0040108Emovesp,ebp 40.00401090popebp 41.00401091ret
第1~10行进行执行函数内代码的准备工作,保存现场;
第2行对堆栈进行移动;
第3行则意味着max函数为其内部局部变量准备的堆栈空间为50h字节;
第11行表示把变量n 的内存空间安排在了函数内部局部栈底减8的位置(占用4个字节);
第12~13行非常关键,对应着va_start ( ap, num),这两行将第一个可变参数的地址赋值给了指针ap;
从第12行可以看出num 的地址为ebp+0Ch;
从第13行可以看出ap 被分配在函数内部局部栈底减4 的位置上(占用4 个字节);
第22~27行最为关键,对应着va_arg (ap, int);
第22~24行的作用为将ap 指向下一可变参数(可变参数的地址间隔为4 个字节,从add eax,4 可以看出);
第25~27行则取当前可变参数的值赋给变量t。这段反汇编很奇怪,它先移动可变参数指针,再在赋值指令里面回过头来取先前的参数值赋给t(从mov edx,dword ptr [ecx-4]语句可以看出);
第36~41行恢复现场和堆栈地址,执行函数返回操作。
审核编辑:汤梓红
-
C语言
+关注
关注
180文章
7597浏览量
136133 -
编程语言
+关注
关注
10文章
1938浏览量
34593 -
函数
+关注
关注
3文章
4304浏览量
62416 -
C++
+关注
关注
22文章
2104浏览量
73482 -
可变参数
+关注
关注
0文章
2浏览量
4814
原文标题:C语言可变参数的使用详解
文章出处:【微信号:mcu168,微信公众号:硬件攻城狮】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论