首页 > 其他分享 >C语言可变参数的使用详解

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

时间:2023-02-24 17:23:14浏览次数:44  
标签:va int mov C语言 ap 详解 可变 dword ptr

一、可变参数表介绍

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 等确定)。

     

四、运行机制

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

1. 004010C8 push 5
2. 004010CA push 8
3. 004010CC push 3
4. 004010CE push 6
5. 004010D0 push 5
6. 004010D2 push 5
7. 004010D4 call @ILT+5(max) (0040100a)

 

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

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

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

int max ( int num, ...) {
1. 00401020 push ebp
2. 00401021 mov ebp,esp
3. 00401023 sub esp,50h
4. 00401026 push ebx
5. 00401027 push esi
6. 00401028 push edi
7. 00401029 lea edi,[ebp-50h]
8. 0040102C mov ecx,14h
9. 00401031 mov eax,0CCCCCCCCh
10. 00401036 rep stos dword ptr [edi]
    va_list ap;
    int m = -0x7FFFFFFF; /* 32 系统中最小的整数 */
11. 00401038 mov dword ptr [ebp-8],80000001h
    va_start ( ap, num );
12. 0040103F lea eax,[ebp+0Ch]
13. 00401042 mov dword ptr [ebp-4],eax
for ( int i= 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. 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],0
    return m;
35. 00401088 mov eax,dword ptr [ebp-8]
    }
36. 0040108B pop edi
37. 0040108C pop esi
38. 0040108D pop ebx
39. 0040108E mov esp,ebp
40. 00401090 pop ebp
41. 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行恢复现场和堆栈地址,执行函数返回操作。

标签:va,int,mov,C语言,ap,详解,可变,dword,ptr
From: https://www.cnblogs.com/rongjiangwei/p/17152259.html

相关文章

  • K8SYaml文件详解
    一、K8S支持的文件格式kubernetes支持YAML和JSON文件格式管理资源对象。JSON格式:主要用于api接口之间消息的传递YAML格式:用于配置和管理,YAML是一种简洁的非标记性语言,内......
  • SkeyeLive中DShow本地采集视频参数设置及可能出现的错误提示详解
    在近期发布的SkeyeLive多窗口版本中,由于界面的局限性,选择性的将本地采集的音视频参数设置在界面上剔除掉了(暂时还没想好放在哪里,后续版本会在界面调整后添加),大家可以查看Ske......
  • C语言经典习题(六)
    1.计算偶数的所有质因子输入一个正整数,按照从小到大的顺序输出它的所有质因子(重复的也要列举)输入一个整数输出描述:按照从小到大的顺序输出它的所有质数的因子,以空格隔开。......
  • 动态规划(DP算法)详解
    什么是动态规划:动态规划_百度百科内容太多了不作介绍,重点部分是无后效性,重叠子问题,最优子结构。问S->P1和S->P2有多少种路径数,毫无疑问可以先从S开始深搜两次,S->P1和S->......
  • c语言之各种printf(printf, sprintf, snprintf, swprintf, fprintf, fwprintf, vsprin
    一、v|s|f|n|w的含义v:参数作为va_list一个整体传入s:输出对象为内存缓冲区(char*,wchar_t*)f:输出对象为文件流(char*,wchar_t*)w:宽字符串版本n......
  • 【Git】010-Git分支Git 基本使用及工作流程详解:
     目录​​一、概述​​​​二、git分支中常用指令​​​​三、补充​​​​四、Git官方中文文档​​​​官方文档:​​​​推荐网站:​​​​五、不错的文章​​​​超详细!Gi......
  • 模型压缩-剪枝算法详解
    一,前言学术界的SOTA模型在落地部署到工业界应用到过程中,通常是要面临着低延迟(Latency)、高吞吐(Throughpout)、高效率(Efficiency)挑战的。而模型压缩算法可以将一个庞大而复......
  • 一文详解 Netty 组件
    作者:京东物流张弓言一、背景Netty是一款优秀的高性能网络框架,内部通过NIO的方式来处理网络请求,在高负载下也能可靠和高效地处理I/O操作作为较底层的网络通信框架,......
  • 一文详解 Netty 组件
    作者:京东物流张弓言一、背景Netty是一款优秀的高性能网络框架,内部通过NIO的方式来处理网络请求,在高负载下也能可靠和高效地处理I/O操作作为较底层的网络通信框架,其被......
  • 云原生|kubernetes|kubernetes中的资源(一)---service详解
    前言:每个Pod都有自己的IP地址,但是如果Pod重新启动了的话那么他的IP很有可能也就变化了。这就会带来一个问题:例如我们有一些后端的Pod的集合为集群中的其他前端的......