搜索
查看
编辑修改
首页
UNITY
NODEJS
PYTHON
AI
GIT
PHP
GO
CEF3
JAVA
HTML
CSS
搜索
我家小花儿
这个屌丝很懒,什么也没留下!
关注作者
热门标签
jquery
HTML
CSS
PHP
ASP
PYTHON
GO
AI
C
C++
C#
PHOTOSHOP
UNITY
iOS
android
vue
xml
爬虫
SEO
LINUX
WINDOWS
JAVA
MFC
CEF3
CAD
NODEJS
GIT
Pyppeteer
article
热门文章
1
Power BI 常见数据导入错误(查询超时、找不到文件、数据类型错误、找不到表格)_powerbi如何查看错误
2
(微信小程序毕业设计)理发店预约系统项目开发实例(附源码+论文)_线上预约小程序开发
3
鸡鸭养殖户养殖决策系统:基于python爬虫鸡鸭电商销售数据可视化分析(django框架)
4
Iterator迭代器
5
DASCTF X CBCTF 2022九月挑战赛 REVERSE landing_dasctf x cbctf landing
6
100个精选zencart扩展插件
7
python数据分析实战:用LSTM模型预测时间序列(以原油价格预测为例)_油温时间序列数据lstm
8
有赞HBase技术实践:读流程解析与优化
9
DICOM中几个判断图像方向的tag
10
关于802.1x认证的那些事(一)_802.1x认证 老化时间
当前位置:
article
> 正文
如何判别可变参数函数的参数类型? _c如何知道可变参数是什么类型
作者:我家小花儿 | 2024-02-27 21:52:42
赞
踩
c如何知道可变参数是什么类型
一、什么是可变参数 我们在C语言编程中有时会遇到一些参数个数可变的函数,例如printf()函数,其函数原型为: int printf( const char* format, ...); 它除了有一个参数format固定以外,后面跟的参数的个数和类型是可变的(用三个点“…”做参数占位符),实际调用时可以有以下的形式: printf("%d",i); printf("%s",s); printf("the number is %d ,string is:%s", i, s); 以上这些东西已为大家所熟悉。但是究竟如何写可变参数的C函数以及这些可变参数的函数编译器是如何实现,这个问题却一直困扰了我好久。本文就这个问题进行一些探讨,希望能对大家有些帮助. 二、写一个简单的可变参数的C函数 先看例子程序。该函数至少有一个整数参数,其后占位符…,表示后面参数的个数不定. 在这个例子里,所有的输入参数必须都是整数,函数的功能只是打印所有参数的值. 函数代码如下: //示例代码1:可变参数函数的使用 #include "stdio.h" #include "stdarg.h" void simple_va_fun(int start, ...) { va_list arg_ptr; int nArgValue =start; int nArgCout=0; //可变参数的数目 va_start(arg_ptr,start); //以固定参数的地址为起点确定变参的内存起始地址。 do { ++nArgCout; printf("the %d th arg: %d/n",nArgCout,nArgValue); //输出各参数的值 nArgValue = va_arg(arg_ptr,int); //得到下一个可变参数的值 } while(nArgValue != -1); return; } int main(int argc, char* argv[]) { simple_va_fun(100,-1); simple_va_fun(100,200,-1); return 0; } 下面解释一下这些代码 从这个函数的实现可以看到,我们使用可变参数应该有以下步骤: ⑴由于在程序中将用到以下这些宏: void va_start( va_list arg_ptr, prev_param ); type va_arg( va_list arg_ptr, type ); void va_end( va_list arg_ptr ); va在这里是variable-argument(可变参数)的意思. 这些宏定义在stdarg.h中,所以用到可变参数的程序应该包含这个头文件. ⑵函数里首先定义一个va_list型的变量,这里是arg_ptr,这个变 量是存储参数地址的指针.因为得到参数的地址之后,再结合参数的类型,才能得到参数的值。 ⑶然后用va_start宏初始化⑵中定义的变量arg_ptr,这个宏的第二个参数是可变参数列表的前一个参数,即最后一个固定参数. ⑷然后依次用va_arg宏使arg_ptr返回可变参数的地址,得到这个地址之后,结合参数的类型,就可以得到参数的值。 ⑸设定结束条件,这里的条件就是判断参数值是否为-1。注意被调的函数在调用时是不知道可变参数的正确数目的,程序员必须自己在代码中指明结束条件。至于为什么它不会知道参数的数目,读者在看完这几个宏的内部实现机制后,自然就会明白。 (二)可变参数在编译器中的处理 我们知道va_start,va_arg,va_end是在stdarg.h中被定义成宏的, 由于1)硬件平台的不同 2)编译器的不同,所以定义的宏也有所不同,下面看一下VC++6.0中stdarg.h里的代码(文件的路径为VC安装目录下的/vc98/include/stdarg.h) typedef char * va_list; #define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) #define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) #define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) #define va_end(ap) ( ap = (va_list)0 ) 下面我们解释这些代码的含义: 1、首先把va_list被定义成char*,这是因为在我们目前所用的PC机上,字符指针类型可以用来存储内存单元地址。而在有的机器上va_list是被定义成void*的 2、定义_INTSIZEOF(n)主要是为了某些需要内存的对齐的系统.这个宏的目的是为了得到最后一个固定参数的实际内存大小。在我的机器上直接用sizeof运算符来代替,对程序的运行结构也没有影响。(后文将看到我自己的实现)。 3、va_start的定义为 &v+_INTSIZEOF(v) ,这里&v是最后一个固定参数的起始地址,再加上其实际占用大小后,就得到了第一个可变参数的起始内存地址。所以我们运行va_start(ap, v)以后,ap指向第一个可变参数在的内存地址,有了这个地址,以后的事情就简单了。 这里要知道两个事情: ⑴在intel+windows的机器上,函数栈的方向是向下的,栈顶指针的内存地址低于栈底指针,所以先进栈的数据是存放在内存的高地址处。 (2)在VC等绝大多数C编译器中,默认情况下,参数进栈的顺序是由右向左的,因此,参数进栈以后的内存模型如下图所示:最后一个固定参数的地址位于第一个可变参数之下,并且是连续存储的。 |——————————————————————————| | 最后一个可变参数 | ->高内存地址处 |——————————————————————————| ................... |——————————————————————————| | 第N个可变参数 | ->va_arg(arg_ptr,int)后arg_ptr所指的地方, | | 即第N个可变参数的地址。 |——————————————— | …………………………. |——————————————————————————| | 第一个可变参数 | ->va_start(arg_ptr,start)后arg_ptr所指的地方 | | 即第一个可变参数的地址 |——————————————— | |———————————————————————— ——| | | | 最后一个固定参数 | -> start的起始地址 |—————————————— —| ................. |—————————————————————————— | | | |——————————————— | -> 低内存地址处 (4) va_arg():有了va_start的良好基础,我们取得了第一个可变参数的地址,在va_arg()里的任务就是根据指定的参数类型取得本参数的值,并且把指针调到下一个参数的起始地址。 因此,现在再来看va_arg()的实现就应该心中有数了: #define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) 这个宏做了两个事情, ①用用户输入的类型名对参数地址进行强制类型转换,得到用户所需要的值 ②计算出本参数的实际大小,将指针调到本参数的结尾,也就是下一个参数的首地址,以便后续处理。 (5)va_end宏的解释:x86平台定义为ap=(char*)0;使ap不再 指向堆栈,而是跟NULL一样.有些直接定义为((void*)0),这样编译器不会为va_end产生代码,例如gcc在linux的x86平台就是这样定义的. 在这里大家要注意一个问题:由于参数的地址用于va_start宏,所以参数不能声明为寄存器变量或作为函数或数组类型. 关于va_start, va_arg, va_end的描述就是这些了,我们要注意的 是不同的操作系统和硬件平台的定义有些不同,但原理却是相似的. (三)可变参数在编程中要注意的问题 因为va_start, va_arg, va_end等定义成宏,所以它显得很愚蠢, 可变参数的类型和个数完全在该函数中由程序代码控制,它并不能智能 地识别不同参数的个数和类型. 有人会问:那么printf中不是实现了智能识别参数吗?那是因为函数 printf是从固定参数format字符串来分析出参数的类型,再调用va_arg 的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通过在自己的程序里作判断来实现的. 例如,在C的经典教材《the c programming language》的7.3节中就给出了一个printf的可能实现方式,由于篇幅原因这里不再叙述。 (四)小结: 1、标准C库的中的三个宏的作用只是用来确定可变参数列表中每个参数的内存地址,编译器是不知道参数的实际数目的。 2、在实际应用的代码中,程序员必须自己考虑确定参数数目的办法,如 ⑴在固定参数中设标志—— printf函数就是用这个办法。后面也有例子。 ⑵在预先设定一个特殊的结束标记,就是说多输入一个可变参数,调用时要将最后一个可变参数的值设置成这个特殊的值,在函数体中根据这个值判断是否达到参数的结尾。本文前面的代码就是采用这个办法. 无论采用哪种办法,程序员都应该在文档中告诉调用者自己的约定。 3、实现可变参数的要点就是想办法取得每个参数的地址,取得地址的办法由以下几个因素决定: ①函数栈的生长方向 ②参数的入栈顺序 ③CPU的对齐方式 ④内存地址的表达方式 结合源代码,我们可以看出va_list的实现是由④决定的,_INTSIZEOF(n)的引入则是由③决定的,他和①②又一起决定了va_start的实现,最后va_end的存在则是良好编程风格的体现,将不再使用的指针设为NULL,这样可以防止以后的误操作。 4、取得地址后,再结合参数的类型,程序员就可以正确的处理参数了。理解了以上要点,相信稍有经验的读者就可以写出适合于自己机器的实现来。下面就是一个例子
声明:
本文内容由网友自发贡献,转载请注明出处:
【wpsshop】
推荐阅读
article
JAVA
编程
多线程
面试常见
知识点
灵魂
拷问
(一)
_
灵魂
拷问
java
弊端...
1、对线程池的了解ThreadPoolExcutor创建线程池,包含七个参数。七个参数分别是核心线程数、最大线程数、任务...
赞
踩
article
数据库
故障
致
美国
超一万
航班
取消或延迟_
tdsql
oceasebase
...
在2023年新年的第二周,
美国
东部时间1月11日上午,6点29分,
美国
航空监管机构(FAA)发布了一条仅40字的通告,随...
赞
踩
article
【
HarmonyOS
】安装DevEco Studio后检查环境出现
ohpm
not
set
up_d...
按照官网的操作方式,下载完
ohpm
总是检测不到,打开cmd,发现我之前因为其他项目改过编码。再去检测一下,发现成功了。_...
赞
踩
article
黑/
白群晖
安装
zerotier
-
docker
完美版_
zerotier
docker
...
正常使用
zerotier
并且能正常使用
zerotier
的自定义路由功能,重启依然完美的正常运行_
zerotier
d...
赞
踩
article
进制
概念和
IP
的换
算
方法
简析
_
ip
进制
怎么
算
技巧...
(10110010)21×2¹+1×2⁴+1×2⁵+1×2⁷=178(1207)81为0012为0100为0007为11...
赞
踩
article
基于
微信
小
程序
的
超市
网络
配货
系统
的设计与实现(源码+论文)_v_145_
微信
小
程序
超市
源码...
该
超市
网络
配货
微信
小
程序
具有操作简单,界面清晰,管理方便,功能完善等优势,有很高的使用价值,通过本
超市
网络
配货
微信
小
程序
...
赞
踩
article
基于
SpringBoot
+Vue
校园
菜鸟
驿站
管理系统
的
设计
与实现...
在此基础上,结合现有
校园
菜鸟
驿站
体系的特点,运用新技术,构建了以springboot为基础的
校园
菜鸟
驿站
信息化管理体系。...
赞
踩
article
69
x 的
平方根
(
牛顿
迭代法
)_
69
ⅹⅹ...
1、题目描述实现int sqrt(int x)函数。计算并返回x的
平方根
,其中x 是非负整数。由于返回类型是整数,结果只...
赞
踩
article
基于
java
springboot
仓库
管理
微信
小
程序
源码_
java
+
微信
小
程序
物资
管理
系统
...
开发环境及工具: 大等于jdk1.8,大于mysql5.5,idea(eclipse),
微信
开发者工具技术说明:spri...
赞
踩
article
requests
.
exceptions
.
MissingSchema
:
Invalid
URL 'xx...
import
requests
session =
requests
.session() carProposalUrl ...
赞
踩
article
智胜
未来
,
新时代
IT
技术
人
风口攻略-第二版(弃稿)...
鸿蒙已经成为行业的焦点
,
未来
的发展潜力无限。作为一名程序员兼UP主
,
我非常荣幸地接受了邀请
,
参加了今年在松山湖举办的 [...
赞
踩
article
Java
可变
参数
...
Java允许将同一个类中多个同名同功能但
参数
个数不同的方法,封装成一个方法。
可变
参数
可以和普通类型的
参数
一起放在形参列表...
赞
踩
article
【
JDK
】Mac版
安装
JDK
并
配置
环境
_
mac
安装
jdk
...
原文地址:https://www.cnblogs.com/dingzhijie/p/7016397.html第一步 : ...
赞
踩
article
音
视频
xxxx
_h
视频
在线...
本文章是以学习笔记形式展开记录。https://ffmpeg.xianwaizhiyin.net/base-knowle...
赞
踩
article
M1 Mac
Java
环境配置和
IDEA
下载
安装
使用_
mac
m1pro
下载
java
...
Java
环境安装和m1版
mac
OS
IDEA
使用_
mac
m1pro
下载
java
mac
m1pro
下载
java
...
赞
踩
article
python
抓包
--
用
wireshark
抓包
、解析_
python
wireshark
...
只捕获源地址为192.168.1.125且目的端口为80的流量:src host 192.168.1.125 && ds...
赞
踩
article
基于
java
web+
mysql
的教务
管理系统
(
java
+jsp+
spring
+
spring
boot+...
基于
java
web+
mysql
的教务
管理系统
(
java
+jsp+
spring
+
spring
boot+layui)运行环境...
赞
踩
article
为什么
域名
与
服务器
绑定
之后不能打开
网站
或“
无法访问
此网页”_
域名
绑定
windows
服务器
ip后无法访...
因为端口范围没有添加80端口。解决:以阿里云
服务器
为例1、点击“网络与安全”,点击“安全组”,点击,点击“配置规则”。2...
赞
踩
article
【实战知识】使用Github
Action
+ Nginx实现
自动化
部署
_
github
asigin
...
大家好啊,我是独立开发豆小匠。先说一下背景~我的小程序:豆流便签,目前使用云托管
部署
后端服务,使用轻量级服务器
部署
数据库...
赞
踩
article
mac
使用
Java
命令
运行
Java
程序_
mac
运行
javac
命令
...
1、使用文本软件写一个简单的
Java
程序,保存为后缀为.java文件_
mac
运行
javac
命令
mac
运行
javac
命令
...
赞
踩
相关标签
多线程
java
jvm
面试
数据库
大数据
云计算
数据库架构
数据库开发
harmonyos
华为
docker
容器
运维
linux
微信小程序
小程序
hololens
xamarin
vscode
typescript
azure
spring boot
vue.js