关于参数处理那点事,C标准库反汇编解析

关于参数处理那点事,C标准库反汇编解析

在这里插入图片描述

1 stdarg.h 内容概览

这个头文件用于提供访问无名参数(既没有命名也没有类型)的类型和宏。

假设函数形如:

void functionWithMltipleInput(normalType n, ...)

第一个参数名为n,后续省略号表示有不定量个类型未知的参数。

为了在函数中处理这些参数,我们需要一组方法来获取无名参数的值。

stdarg.h提供了1个类型和4个函数式的宏来满足上述需求。可以说这个头文件相当简单了。

  • va_list 变量参数列表(指针),用于存放参数的信息
  • va_start 初始化变量参数列表的宏
  • va_arg 用于获取下一个参数的宏
  • va_end 用于表明结束使用va_list的宏
  • va_copy 复制变量参数列表

va_start()

void va_start (va_list ap, paramN);

初始化变量ap, 使其指向第一个无名参数

  • ap 未初始化的va_list,实质上是一个char*类型的指针。
  • paramN 最后一个显示参数(named parameter)的变量名

va_arg()

type va_arg (va_list ap, type)

将p指向的参数解析为type类型,同时修改ap,使其指向参数列表中的下一个参数。

也就是说,初始化ap后连续两次调用va_arg,第一次返回第一个无名参数的值,第二次返回的就是第二个无名参数的值了。

va_end()

void va_end (va_list ap);

要求只要使用了va_start,就必须在退出函数之前调用va_end,不知道为啥,不清楚有啥作用。

va_copy()

没啥用。


example

下面的例程将首先用

va_start(vl, argn);

初始化参数列表指针vl,而后用

val = va_arg(vl,double);

获取下一个参数并将其视为double赋值给val \

src

#include <stdarg.h>
#include <stdio.h>
typedef int normalType;
void minput(normalType argn, ...)
{int i;int val;printf ("Printing unnamed arguments:");va_list vl;va_start(vl, argn);for (i=0;i<argn;i++){val=va_arg(vl,int);printf(" [%2d]",val);}va_end(vl);printf("\n");
}int main()
{minput(3, 1, 2, 3);
}

output

Printing unnamed arguments: [ 1] [ 2] [ 3]

如下可以看到打印出无名参数的值与调用输入一致。


2 用法反汇编分析

处理

编译

D:\TempWorkSpace\c> gcc .\args.c -o arg.exe

运行

D:\TempWorkSpace\c> .\arg.exe
Printing unnamed arguments: [ 1] [ 2] [ 3]

反汇编

D:\TempWorkSpace\c> objdump -D .\arg.exe > arg.asm

分析

下图可见0x4015c3为main函数的起始地址

00000000004015c3 <main>

运行到0x4015e6处调用函数minput(),跳转到地址0x401550,可见传参3,2,1,3与调用输入一致

callq 401550 <[minput]>

而后在0x4015f5处退出主函数

4015f6 90 retq

在这里插入图片描述

下图可见函数minput()的反汇编结果,函数起始地址为0x401550

图中右侧蓝色高亮部分对应图中左侧函数体中的for循环,可见遍历变量参数列表过程,总共循环三次
在这里插入图片描述

理解

传参意味着将参数值压入栈中,访问参数也就是按参数偏移取值。

va_list就是一个用于取值的指针,使用前用va_start(AP, LASTARG)找到第一个无名变量的基地址,也就是va_list的初始化。

va_arg(AP, TYPE)的作用就是把对应的值设定类型解析出来,操作上类似于*(type*)<va_list>(把va_list当成type类型的指针解引用)。

va_end(va_list)就有点像是注脚了,表明完事了,基本没啥用处,而va_copy()就属于锦上添花,意义不明了。

3 回到标准库源码

标准库通常是宏名下划线预处理乱飞,为了聚焦重点,这里把各种乱七八糟的东西都剔掉了。

#ifndef _STDARG_H
#define _STDARG_H#ifdef __GNUC__
/* The GNU C-compiler uses its own, but similar varargs mechanism. */
typedef char *va_list;
/* Amount of space required in an argument list for an arg of type TYPE.
TYPE may alternatively be an expression whose type is used. */
#define __va_rounded_size(TYPE)\(((sizeof (TYPE) + sizeof (int) - 1) / sizeof (int)) * sizeof (int))
#if __GNUC__ < 2
#else    /* __GNUC__ >= */
#ifndef __sparc__
#define va_start(AP, LASTARG)\(AP = ((char *) __builtin_next_arg ()))
#else
#endif
void va_end (va_list); /* Defined in libgcc.a */
#define va_end(AP)
#define va_arg(AP, TYPE)\(AP = ((char *) (AP)) += __va_rounded_size (TYPE),\*((TYPE *) ((char *) (AP) - __va_rounded_size (TYPE))))
#endif    /* __GNUC__ >= */
#else    /* not __GNUC__ */
#endif /* __GNUC__ */
#endif /* _STDARG_H */

可以看到va_start(AP, LASTARG)的实现就是返回一个char*类型的指针,通过__builtin_next_arg获取最后一个显式参数的下一个参数占据的地址。

#define va_start(AP, LASTARG) (AP = ((char *) __builtin_next_arg ()))

va_arg(AP, TYPE)则包含两个操作,修改AP的值

((char *) (AP)) += __va_rounded_size (TYPE)

返回AP当前指向的参数

*((TYPE *) ((char *) (AP) - __va_rounded_size (TYPE)))

拼在一起

#define va_arg(AP, TYPE)                        \(AP = ((char *) (AP)) += __va_rounded_size (TYPE),            \*((TYPE *) ((char *) (AP) - __va_rounded_size (TYPE))))

va_end(AP)则真的啥也没干,挺好,不用分析了

#define va_end(AP)

4 参考

  1. GNU doc, Varargs.html
  2. Learn.microsoft, c-runtime-library

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/485056.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C++精简版回顾】6.构造函数

一。类的四种初始化方式 1.不使用构造函数初始化类 使用函数引用来初始化类 class MM { public:string& getname() {return name;}int& getage() {return age;}void print() {cout << "name: " << name << endl << "age: &quo…

【2024软件测试面试必会技能】

Unittest(5)&#xff1a;unittest_忽略用例 忽略用例 在执行测试脚本的时候&#xff0c;可能会有某几条用例本次不想执行&#xff0c;但又不想删也 不想注释&#xff0c;unittest通过忽略部分测试用例不执行的方式&#xff0c;分无条件忽略和有条 件忽略,通过装饰器实现所描述…

基于Embedding召回和DSSM双塔模型

文章目录 基于Embedding召回介绍基于Embedding召回算法分类I2I召回U2I召回 DSSM模型DSSM双塔模型层次 基于Embedding召回介绍 基于embedding的召回是从内容文本信息和用户查询的角度出发&#xff0c;利用预训练的词向量模型或深度学习模型&#xff0c;将文本信息转换成向量进行…

进程线程间的通信:2024/2/22

作业1&#xff1a;代码实现线程互斥机制 代码&#xff1a; #include <myhead.h>//临界资源 int num10;//创建一个互斥锁 pthread_mutex_t mutex;//任务一 void *task1(void *arg) {//获取锁资源pthread_mutex_lock(&mutex);num123;sleep(3);printf("task1:num…

【打工日常】使用docker部署StackEdit编辑器-Markdown之利器

一、StackEdit介绍 StackEdit一款强大的在线Markdown编辑器&#xff0c;不仅具备卓越的写作功能&#xff0c;还支持实时预览、多设备同步等特性。 很多时候基于安全和信息保密的关系&#xff0c;建议放在自己的服务器或者本地linux去运行&#xff0c;这样会比较省心。 二、本次…

电商数据采集+跨境电商|API电商数据采集接口洞悉数字新零售发展

随着全球经济一体化和电子商务的快速发展&#xff0c;网络购物的需求日益增加。不断涌现的电商企业使得行业的竞争情况愈演愈烈。在这种情况下&#xff0c;企业不仅要加大经营力度&#xff0c;还要在自己的基础设施和技术上持续投入&#xff0c;才能更好的适应市场和消费习惯。…

船运物流管理系统|基于springboot船运物流管理系统设计与实现(源码+数据库+文档)

船运物流管理系统目录 目录 基于springboot船运物流管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、管理员登录 2、货运单管理 3、公告管理 4、公告类型管理 5、新闻管理 6、新闻类型管理 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考 …

2024.2.22

P1162 #include<map> #include<vector> #include<iostream> #include<math.h> #include<algorithm> #include<string> using namespace std; const int N 1020; int n; int g[N][N];//标记数组 int a[N][N];//储存数组 int dx[] { -1…

《VitePress 简易速速上手小册》第9章 VitePress 的扩展与插件(2024 最新版)

文章目录 9.1 插件生态系统概述9.1.1 基础知识点解析9.1.2 重点案例&#xff1a;SEO 优化插件9.1.3 拓展案例 1&#xff1a;社交分享插件9.1.4 拓展案例 2&#xff1a;内容搜索插件 9.2 常用插件介绍与应用9.2.1 基础知识点解析9.2.2 重点案例&#xff1a;使用 SEO 插件9.2.3 拓…

day6 2/22

1> 将互斥机制的代码实现重新敲一遍 #include<myhead.h> int num520; pthread_mutex_t mutex;//创建互斥锁 void*task1(void*arg) {pthread_mutex_lock(&mutex);sleep(3);num;printf("%d\n",num);pthread_mutex_unlock(&mutex);pthread_exit(NULL)…

300分钟吃透分布式缓存-10讲:MC是怎么定位key的?

我们在进行 Mc 架构剖析时&#xff0c;除了学习 Mc 的系统架构、网络模型、状态机外&#xff0c;还对 Mc 的 slab 分配、Hashtable、LRU 有了简单的了解。本节课&#xff0c;将进一步深入学习这些知识点。 接下来&#xff0c;进入 Memcached 进阶的学习。会讲解 Mc 是如何进行…

《游戏引擎架构》 -- 学习4

资源及文件系统 文件系统 游戏引擎的文件系统API通常提供以下功能&#xff1a; 搜需路径&#xff1a;是含一串路径的字符串&#xff0c;各路径之间以特殊字符&#xff08;如冒号或分号&#xff09;分隔&#xff0c;找文件时就会从这些路径进行搜寻。例如在命令行下执行程序&a…