搜索
查看
编辑修改
首页
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
TCP通信 -- 接收并反馈(全)_tcp接收数据
2
Android APIs (Class Index - Android SDK)(一)_cursoranchorinfo.getinsertionmarkerhorizontal()
3
【Ubuntu】通过网线连接两台电脑以实现局域网连接的方法
4
中美两大AI巨头火了!一位是Sam Altman,一位是「李一舟」
5
python将汉字转化为拼音_python汉字转拼音
6
mac终端编译运行java_java 工程 mac系统如何用命令行编译
7
Axure RP的认识
8
机器学习笔记-多分类下的召回率和F值_多分类的召回率
9
zabbix监控交换机
10
pytorch 数据集划分_pytorch dataset split
当前位置:
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博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:
https://www.wpsshop.cn/w/我家小花儿/article/detail/156122
推荐阅读
article
【
GitHub
项目
推荐--
微软
开源
了
一组
工具
PowerToys
】【转载】...
Microsoft
PowerToys
是一套由
微软
官方开发的免费实用
工具
集,旨在帮助高级用户优化和个性化他们的 Win...
赞
踩
article
深度
学习中
Epoch
和
Batch
Size
的关系_
batchsize
和
epoch
关系...
在
深度
学习中,
Epoch
(周期)和
Batch
Size
(批大小)是训练神经网络时经常使用的两个重要的超参数。_batc...
赞
踩
article
【自用】
基本
linux
命令
(
FinalShell
)...
在finalshell上运行程序的一些Linux指令【自用】
基本
linux
命令
(
FinalShell
) ...
赞
踩
article
【
Java
基础教程
】(二十一)
Java
新
特性
篇 · 第一讲:
可变
参数
——概念及优缺点、特征及
应用
场景
...
Java
提供了一项强大而灵活
的
特性
——
可变
参数
(Variable Arguments)。通过灵活运用
可变
参数
,我们可以让...
赞
踩
article
关于
Deepin23
下的
CUDA
安装_
deepin23
nvidia
...
一般情况下你直接装的驱动当中是包含有
CUDA
的,不必去
nvidia
官网找那个
CUDA
教程,Deepin和Ubuntu一样...
赞
踩
article
Java
--
Mac
系统
终端
编译
运行
java
文件_
mac
终端
运行
java
...
而且非_
mac
终端
运行
java
mac
终端
运行
java
首先声明一下,本博客是建立在
Mac
电脑上已...
赞
踩
article
人才
缺口50万以上
,
平均薪资20K?
网络安全
,
测试
员
的
下
一个
风口~_
网络安全
软件市场
测试
需求
...
网络安全
,
随着进入数字经济时代
,
这四个词被提到
的
频率也越来越高
,
在过去
的
2021年
,
“数据泄露”这样
的
字眼时常浮现在我们...
赞
踩
article
启动A
p
ache服务时报错,Job for
htt
p
d
.
service
failed
because
...
这时我们用systemctl status
htt
p
d
.
service
查看服务的状态,发现80端口被占用。
p
s -ef...
赞
踩
article
选
高防
服务器
要
注意
哪些方面
?——微云网络_
高防
服务器
选取
的
注意
点
...
现在,互联网行业已经越来越火热,其中有些公司或个人抓住了互联网
的
“尾巴”,开始在网上做生意,即做互联网生意。商业中存在着...
赞
踩
article
设置/
修改
nnUNet
训练
的
Epoch
、显卡号_
epoch
默认
100
0
,
训练
中途通过命令行
修改
为10...
nnUNet
训练
的
epoch
、显卡号、cuda_
epoch
默认
100
0
,
训练
中途通过命令行
修改
为
100
,
有效吗
epoch
...
赞
踩
article
实时
数据库
,
内存
数据库
,
关系
型
数据库
比较_
内存
关联
和
数据库
关联
区别...
内存
数据库
内存
数据库
就是将数据放在
内存
中直接操作的
数据库
,
它利用
内存
的读写速度比磁盘快、
内存
是随机访问而磁盘是顺序访问这...
赞
踩
article
五分钟
使用
WebStack
构建个人
网址
导航
...
文章目录(一) 前言(二)文档说明(三)安装
使用
(一) 前言从事开发工作的同学肯定都有感觉在工作和学习中需要记录的网站太...
赞
踩
article
Linux
开发工具
vim
以及
git
的使用详解_
git
commit
vim
...
Linux
开发工具
文章目录
Linux
开发工具
vim
的基本概念与操作命令模式常见命令复制与粘贴文本修改替换删除光标定位底行...
赞
踩
article
网络
知识梳理:
网闸
、
单向
网闸
、
双向
网闸
_
网闸
有
哪些
类...
网闸
(Data Diode 或 Network Diode),
单向
网闸
和
双向
网闸
是
网络
安全领域的关键概念,特别是在需要高...
赞
踩
article
Scapy
扫描局域网_
scapy
fcs
...
Scapy
是一个强大的交互式数据包处理程序(使用python编写)。它能够伪造或者解码大量的网络协议数据包,能够发送、捕...
赞
踩
article
Python3
网络
设备
巡检(
交换机
篇)_
python
抓取
设备
信息
...
3、编写邮件,发送邮件。4..._
python
抓取
设备
信息
python
抓取
设备
信息
介绍 只写了...
赞
踩
article
基于
javaweb
的
物资配送
管理系统
_基于
微信
小
程序
的
“带物
小
程序
”...
如果觉得有用,记得点赞关注我哦结果图:在
微信
公众号新白者回复带物
小
程序
即可领取源码一、作品设计
微信
小
程序
,
小
程序
的
一种,...
赞
踩
article
Zabbix
3.4
.3 使用
SNMP
监控
H3C
交换机
端口流量...
一、什么是
SNMP
1、
SNMP
简介
SNMP
(Simple Network Management Protocol,简单...
赞
踩
article
systemctl
常用命令_
systemctl
redaemon
...
systemd 是 Linux 下的一款系统和服务管理器,兼容 SysV 和 LSB 的启动脚本。systemd 的特性...
赞
踩
article
Mac安装
配置
maven
-
Java
项目管理
_zsh
配置
java
maven
...
Mac安装
配置
maven
-
Java
项目管理
1. 安装
配置
maven
2.
配置
文件及IDEA
配置
1. 安装
配置
maven
...
赞
踩
相关标签
microsoft
深度学习
batch
人工智能
linux
python
pytorch
java
jvm
开发语言
后端
java-ee
机器学习
macos
web安全
网络
安全
软件测试
测试工程师
apache
服务器
mysql
webstack
网页