0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

C语言可变参数的使用详解

硬件攻城狮 来源:硬件攻城狮 2023-03-08 09:47 次阅读

一、可变参数表介绍

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

    文章

    7555

    浏览量

    132032
  • 编程语言
    +关注

    关注

    9

    文章

    1898

    浏览量

    33961
  • 函数
    +关注

    关注

    3

    文章

    4154

    浏览量

    61576
  • C++
    C++
    +关注

    关注

    21

    文章

    2074

    浏览量

    73087
  • 可变参数
    +关注

    关注

    0

    文章

    2

    浏览量

    4802

原文标题:C语言可变参数的使用详解

文章出处:【微信号:mcu168,微信公众号:硬件攻城狮】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    C语言可变形参是什么

      C语言允许定义参数数量可变的函数,这称为可变参数函数(variadic function)。
    的头像 发表于 08-18 21:40 1152次阅读

    可变参数函数的实现原理

    ,或者其它方式。在C语言标准头文件stdarg.h里面已经为可变函数定义了几个宏,使用这些宏也可以实现可变参数函数,原理都一样, 细节不再介
    发表于 10-21 22:18

    C语言内存管理详解

    C语言内存管理详解,很不错的一份资料.
    发表于 08-06 23:14

    C语言——可变参数问题.

    ;The value is %d!\n", value);  这种可变参数可以说是C语言一个比较难理解的部分,这里会由几个问题引发一些对它的分析。   注意:在
    发表于 04-20 15:17

    C语言可变参数的定义

    C语言可变参数的定义。//可变参数用...来表示void TRACE(char *forma
    发表于 07-14 07:43

    怎么设计c语言可变参数函数?

    怎么设计c语言可变参数函数
    发表于 10-27 07:10

    C++ 语言命令详解(第二版)

    电子发烧友网站提供《C++ 语言命令详解(第二版).txt》资料免费下载
    发表于 07-28 13:06 0次下载

    C语言详解_ifdef等宏及妙用

    C语言详解_ifdef等宏及妙用的教程
    发表于 11-16 19:03 0次下载

    彻底搞定C语言指针详解完整版

    彻底搞定C语言指针详解完整版。
    发表于 05-10 17:04 0次下载

    ARM_C语言程序设计详解

    ARM_C语言程序设计详解
    发表于 10-27 15:39 32次下载
    ARM_<b class='flag-5'>C</b><b class='flag-5'>语言</b>程序设计<b class='flag-5'>详解</b>

    C语言的精髓——指针详解

    C语言的精髓——指针详解
    发表于 11-30 14:43 17次下载

    单片机C语言和汇编语言混合编程实例详解

    单片机C语言和汇编语言混合编程实例详解
    发表于 08-16 09:50 224次下载

    C语言-函数的可变形参(不定形参)

    这篇文章介绍C语言函数的不定参数可变参数 形参,实现printf一样的传参效果。
    的头像 发表于 08-14 09:58 2145次阅读

    c语言参数的宏定义

    c语言参数的宏定义  C语言宏定义是一种宏替换机制,它可以将一个标识符替换为一个代码片段。宏定义通常在程序中用来方便地进行常量定义或函数模
    的头像 发表于 09-04 17:45 2022次阅读

    C语言中的可变参数介绍

    C 语言为这种情况提供了一个解决方案,它允许您定义一个函数,能根据具体的需求接受可变数量的参数
    发表于 02-28 14:00 190次阅读
    <b class='flag-5'>C</b><b class='flag-5'>语言</b>中的<b class='flag-5'>可变</b><b class='flag-5'>参数</b>介绍