当前位置:首页 > 生活 > 正文

C语言可变参数的使用详解(c语言可变参数函数定义)

一、可变参数表介绍

c/c++语言具备一个不同于其他编程语言的的特性,即支持可变参数

例如C库中的printf,scanf等函数,都支持输入数量不定的参数。例如:

printf(hello world);  ////< 1个参数prinf(%d, a);         ////< 2个参数printf(%d, %d, a, b); ////< 3个参数

printf函数原型为 int printf(const char *format, …);

从printf的原型来看,其除了接受一个固定参数format以外,后面的参数使用来表示。

在c/c++语言中,表示可以接受不定数量的参数。

二、可变参数表用法

在标准C/C++中,头文件中定义了如下三个宏:

void va_start ( va_list arg_ptr, prev_param ); /* ANSI version */type va_arg ( va_list arg_ptr, type );void va_end ( va_list arg_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宏我们可以取得可变参数表的首指针,这个宏的定义为:

#define va_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原型为:

#define va_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宏定义用来结束可变参数的获取,定义为:

#define va_end ( list )


  • va_end ( list )实际上被定义为空,没有任何真实对应的代码,用于代码对称,与va_start对应;
  • 可能发挥代码的“自注释”作用。所谓代码的“自注释”,指的是代码能自己注释自己。

三、可变参数表的简单使用

#include <stdlib.h>#include <stdio.h>#include <stdarg.h>/*** @brief        求n个数中的最大值* @details* @param[in]     num 整数个数* @param[out]    ... 整数* @retval        最大整数* @par*/int max ( int num, ... ) {int m = -0x7FFFFFFF; /* 32 系统中最小的整数 */va_list ap;va_start ( ap, num );for ( int i= 0; i< num; i++ ) {int t = va_arg (ap, int);if ( t > m ) {m = t;}}va_end (ap);return m;}int main ( int argc, char* argv[] ) {int n = 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 等确定)。

嵌入式物联网需要学的东西真的非常多,千万不要学错了路线和内容,导致工资要不上去!

无偿分享大家一个资料包,差不多150多G。里面学习内容、面经、项目都比较新也比较全!某鱼上买估计至少要好几十。

点击这里找小助理0元领取:点击文中蓝色字体领取吖




四、运行机制

反汇编是研究语法深层特性的终极良策,首先查看main函数中调用max函数时的反汇编:

1. 004010C8 push 52. 004010CA push 83. 004010CC push 34. 004010CE push 65. 004010D0 push 56. 004010D2 push 57. 004010D4 call @ILT+5(max) (0040100a)


  • 第一步:将参数从右向左入栈(第1~6行)
  • 第二步:调用call 指令进行跳转(第7行)

这两步包含了深刻的含义,它说明C/C++默认的调用方式为由调用者管理参数入栈的操作,且入栈的顺序为从右至左,这种调用方式称为_cdecl调用。

x86系统的入栈方向为从高地址到低地址,故第1至n个参数被放在了地址递增的堆栈内。在被调用函数内部,读取这些堆栈的内容就可获得各个参数的值,让我们反汇编到max函数的内部。

int max ( int num, ...) {1. 00401020 push ebp2. 00401021 mov ebp,esp3. 00401023 sub esp,50h4. 00401026 push ebx5. 00401027 push esi6. 00401028 push edi7. 00401029 lea edi,[ebp-50h]8. 0040102C mov ecx,14h9. 00401031 mov eax,0CCCCCCCCh10. 00401036 rep stos dword ptr [edi]va_list ap;int m = -0x7FFFFFFF; /* 32 系统中最小的整数 */11. 00401038 mov dword ptr [ebp-8],80000001hva_start ( ap, num );12. 0040103F lea eax,[ebp+0Ch]13. 00401042 mov dword ptr [ebp-4],eaxfor ( int i= 0; i< num; i++ )14. 00401045 mov dword ptr [ebp-0Ch],015. 0040104C jmp max+37h (00401057)16. 0040104E mov ecx,dword ptr [ebp-0Ch]17. 00401051 add ecx,118. 00401054 mov dword ptr [ebp-0Ch],ecx19. 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,424. 00401065 mov dword ptr [ebp-4],eax25. 00401068 mov ecx,dword ptr [ebp-4]26. 0040106B mov edx,dword ptr [ecx-4]27. 0040106E mov dword ptr [t],edxif ( t > m )28. 00401071 mov eax,dword ptr [t]29. 00401074 cmp eax,dword ptr [ebp-8]30. 00401077 jle max+5Fh (0040107f)m = t;31. 00401079 mov ecx,dword ptr [t]32. 0040107C mov dword ptr [ebp-8],ecx}33. 0040107F jmp max+2Eh (0040104e)va_end (ap);34. 00401081 mov dword ptr [ebp-4],0return m;35. 00401088 mov eax,dword ptr [ebp-8]}36. 0040108B pop edi37. 0040108C pop esi38. 0040108D pop ebx39. 0040108E mov esp,ebp40. 00401090 pop ebp41. 00401091 ret


  • 第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行恢复现场和堆栈地址,执行函数返回操作。

END

文章链接:https://mp.weixin.qq.com/s/3m_6M9pGGoBhYJuTjn2D_g

转载自:嵌入式微处理器

文章来源:嵌入式基地

文章链接:C语言可变参数的使用详解

版权申明:本文来源于网络,免费传达知识,版权归原作者所有。如涉及作品版权问题,请联系我进行删除。