当前位置: 首页 > news >正文

C语言函数可变长度参数剖析

C语言中的很多函数的入参被定义为可变参数,最典型的

int printf (const char * fmt, ...)

要对其中的可变参数进行处理,就要用到va_list类型和 VA_START, VA_END, VA_ARG 宏 ,需要包含<stdarg.h>头文件

 

利用va族函数对不定参数进行解析的过程所示如下:

 1 int my_printf(const char * fmt, ...)
 2 {
 3     va_list struAp;
 4     va_start(struAp, fmt);
 5 
 6     for (; *fmt; ++fmt)
 7     {
 8         if(*fmt != '%')
 9         {
10             PUTC(*fmt); 
11             continue;
12         }
13         
14         fmt++;
15         
16         switch (*fmt)
17         {
18             case 'd':
19             {
20                 int i = va_arg(struAp,int);
21                 PUTC(i);
22             }
23             break;
24 
25             default:
26                 break;
27         }
28     }
29     
30     va_end(struAp);
31 }

 

要了解不定参数的处理方式,就要搞清楚va族函数的实现

#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

 

/* 实质就是一个char型的指针 */

typedef char * va_list;

/* 将指针偏移一个v的长度,指向后面的地址 */

#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )

/* 根据参数类型,取出后面的数据,强制类型转换 */

#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

/* 将指针置为NULL */

#define va_end(ap) ( ap = (va_list)0 )

 

通过解析fmt字符串,得到后面的参数类型和个数,根据参数类型再加上偏移量就可以找到栈中的不定参数了

 

 函数调用和传参的过程所示如下:

将函数参数与函数调用后下一条指令的地址都压入栈中,然后跳到函数的入口地址。

例如

void func(int param1, double param2,int param3){ }

int main()
{ 
    func(3, 1.2, 4); 
    printf("Over\n"); //设指令地址为0x1234
    return 0;
}

 

执行f(3, 1.2, 4)的函数调用,进入func函数时的堆栈如下:

 
 这样,通过param1的地址就可以计算出param2与param3的地址:
 
使用不定参函数的注意事项
 
  • 在C语言中,调用一个可变参数函数时,调用者会对每个参数执行“默认实际参数提升(default argument promotions)”

     ——float类型的实际参数将提升到double
  ——char类型的实际参数将提升到int
  ——short类型的实际参数将提升到int

  • 在没有函数原型的情况下,char与short类型都将被转换为int类型,float类型将被转换为double类型。

             ——《C语言程序设计》第2版  2.7 类型转换 p36

  • 这样写肯定是不对的:

    c = va_arg(ap,char);

  因为我们无法传递一个char类型参数,如果传递了,它将会被自动转化为int类型。上面的式子应该写成:
  c = va_arg(ap,int);

                ——《C陷阱与缺陷》p164

转载于:https://www.cnblogs.com/li-hao/p/3401942.html

相关文章:

  • 当本机通过代理服务器上网时,本机无法打开在本机上的虚拟机(oracle vm)linux系统上的网站,但是局域网里的其他机器却可以打开...
  • API编程基本控件使用
  • SCOM 常识概念—图标状态/维护模式
  • 学习电商路线
  • SDL2.0上手试用
  • static 与 extern 关键字描述说明
  • Java对象引用处理机制
  • HTML5 UI框架Kendo UI Web中如何创建自定义组件(二)
  • WIKI系统及MysQL数据库宕机恢复文档
  • Intellij IDEA快捷键
  • OpenCV学习(38) 人脸识别(3)
  • Android 开发:view的几种布局方式及实践
  • Java深度历险(十)——Java对象序列化与RMI
  • Codeforces Round #215 (Div. 2) 解题报告
  • CGI Internal Server Error
  • ----------
  • css的样式优先级
  • docker容器内的网络抓包
  • DOM的那些事
  • ES6--对象的扩展
  • Fundebug计费标准解释:事件数是如何定义的?
  • HashMap剖析之内部结构
  • Java多态
  • Leetcode 27 Remove Element
  • Making An Indicator With Pure CSS
  • mongo索引构建
  • react-native 安卓真机环境搭建
  • RxJS: 简单入门
  • Sass 快速入门教程
  • SQLServer之创建显式事务
  • 对话 CTO〡听神策数据 CTO 曹犟描绘数据分析行业的无限可能
  • 微信小程序设置上一页数据
  • 在Mac OS X上安装 Ruby运行环境
  • 找一份好的前端工作,起点很重要
  • No resource identifier found for attribute,RxJava之zip操作符
  • 你对linux中grep命令知道多少?
  • 《天龙八部3D》Unity技术方案揭秘
  • ​​​​​​​ubuntu16.04 fastreid训练过程
  • # Python csv、xlsx、json、二进制(MP3) 文件读写基本使用
  • #绘制圆心_R语言——绘制一个诚意满满的圆 祝你2021圆圆满满
  • ${factoryList }后面有空格不影响
  • $Django python中使用redis, django中使用(封装了),redis开启事务(管道)
  • (14)目标检测_SSD训练代码基于pytorch搭建代码
  • (PWM呼吸灯)合泰开发板HT66F2390-----点灯大师
  • (备忘)Java Map 遍历
  • (三)docker:Dockerfile构建容器运行jar包
  • (四) Graphivz 颜色选择
  • (转)Windows2003安全设置/维护
  • .NET CORE 3.1 集成JWT鉴权和授权2
  • .NET(C#、VB)APP开发——Smobiler平台控件介绍:Bluetooth组件
  • .NET处理HTTP请求
  • :如何用SQL脚本保存存储过程返回的结果集
  • [ vulhub漏洞复现篇 ] AppWeb认证绕过漏洞(CVE-2018-8715)
  • [@Controller]4 详解@ModelAttribute
  • [1181]linux两台服务器之间传输文件和文件夹