C语言如何编写可变参数函数(涉及到二级指针)

2024-05-11 13:18

本文主要是介绍C语言如何编写可变参数函数(涉及到二级指针),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

C语言中函数支持可变参数,常见的可变参数的函数有printf()函数,他的函数原型是_CRTIMP int __cdecl printf(const char *, ...);

可变函数的核心就在,...这里。


既然printf()函数是可变函数,这里就简单模拟printf()函数的功能写个myprintf()函数

#include <iostream>
using namespace std;
//************************************
// Method:    myprintf
// FullName:  myprintf
// Access:    public 
// Returns:   int
// Qualifier:
// Parameter: const char * fmt
// Parameter: ...
//************************************
int myprintf(const char *fmt,...)
{int i = 0;			//记录传入数组fmt的下表
//	int index = 0;			//输出字符串下标int arg = -1;			//参数个数
//	char putfmt[1024];		//输出字符串char *s;			//临时存储参数int ii=0;bool status = true;		//正确标志//	memset(putfmt,0,1024);int length = strlen(fmt)+1;for(i=0;i<length;i++){if(fmt[i]=='%'){i++;int flag = 0;int *ppp;switch(fmt[i]){case 's':if(-1 == arg)arg = 0;flag = 1;ppp=(int *)&fmt+(1+arg);s = (char *)*ppp;ii = 0;while(s[ii])cout<<s[ii++];//	putfmt[index++]=s[ii++];arg++;break;case 'd':flag = 1;ppp=(int *)&fmt+(1+arg);cout<<*ppp;arg++;break;default:break;}if(0 == flag){status = false;break;}}elsecout<<fmt[i];//	putfmt[index++]=fmt[i];}return arg;}
void main(void)
{myprintf("hello %s","oba没有马!");
}


运行结果(图1):


    图1

大致的原理:

第一个形参起重要作用,用来判断后面有几个参数,其中可以用%d来识别后面有个int类型变量,%s识别后面有个字符串变量,以此类推。取的第一个参数后,可通过指针偏移来获取可变参数的地址,继而进行后续的操作。

以本文章中的例子为例,可通过调试的形式来更形象的解释(图2)。


         图2


其中第一个固定参数的地址为0x0043201c,通过查看内存地址可以看到对应的内容。而第二个参数可变参数紧跟在第一个参数对应的内存地址的后面(内存中并非显示正确的字是由于中文为两个字节,oba有三个字节,后面三个字的编码被错开放了,因此显示不一样的字)。那么,要如何正确的在内存中识别中相应的参数呢?

就上面的代码来说,函数中传递了两个参数,都是字符串类型,而字符串类型在传递给函数的过程中传递的是首地址,也可以理解为指针。既然是指针,而且同为一个函数的参数,那在指针的地址(二级指针)上会不会有所关联?

通过&fmt查看其地址(图3)


         图3


由图3可知,&fmt的地址为0x0019feec,在内存中追踪该地址,可看到在内存中,0x0019feec-0x0019feef对应的值为1C 20 43 00,由于在INTEL CPU中,数据的存储形式为小端模式(低地址存地位,不懂可以看我的另一篇博客大端模式,小端模式的区别),因此&fmt里面的值为0x43201C,该值就是fmt的地址。而&fmt后面跟的四个字节,跟&fmt的值有点相像,取其值0x432028查看对应的地址(图4):



    图4


看到了没有!?这个地址里面的存的值就是第二个参数的的内容!

理一下思路:在调用自定义可变参数函数myprintf("hello %s","oba没有马!");时,总共传递了两个参数,一个固定参数"hello %s",另一个传递了可变参数"oba没有马",这两个参数(一级指针)的内容是非连续的(图1,中间有4个字节的00隔开,后面再讨论),但是它们的地址(二级指针)是连续的(图3)。因此可以通过二级指针的偏移来定义可变参数。

在函数myprintf()中,第一个固定参数fmt的类型为char *,取其地址后为0x0019feec,需要偏移4个字节到下一个地址。偏移到0x0019fef0时,此时为二级指针,需进行降级操作,用取值符*降成一级指针,此时指向地址0x00432028,再取里面的值就是要的可变参数的值了。


前面研究的为一个可变参数,如果更多个呢?那在内存中的形式又是怎么样的?下面用三个可变参数作为测试:

修改后的main函数

void main(void)
{myprintf("hello %s %s %s","oba没有马!","5","you");
}
调试看其内存(图5)


图5


由图5可以看出,参数所占的内存大小以4字节对齐,不足4个字节的补全。参数跟参数之间有4个空白字节作为隔断(图5中的红色框部分)。再看它们的地址(二级指针,图6)


图6

图6中,红框部分为四个参数的二级指针!有兴趣的读者可将四个参数对应的指针代入图5中进行验证。


二级指针:

比如说一个字符串指针,char *arr=“hello",其中"hello"存在一个内存空间中,占5个字节,而指针只能存四个字节,因此要用一个指针表示5个地址的字符串,只能通过地址的形式表示,arr就是一级指针,指向"hello"的内存地址的首地址。现在问题来了,形参表中如何才能做到又能保存字符串,又能保存Int,float等其他类型的变量呢?

答案就是用二级指针(图7)。


把main函数中的代码改成:

void main(void)
{myprintf("hello %s %d %s","world",5,"you");
}
调试时结果如图7所示。

注:本文章的重点在于可变参数函数的研究,不在于printf()的研究,因此printf()只是简单的实现!


这篇关于C语言如何编写可变参数函数(涉及到二级指针)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/979644

相关文章

C语言中联合体union的使用

本文编辑整理自: http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=179471 一、前言 “联合体”(union)与“结构体”(struct)有一些相似之处。但两者有本质上的不同。在结构体中,各成员有各自的内存空间, 一个结构变量的总长度是各成员长度之和。而在“联合”中,各成员共享一段内存空间, 一个联合变量

大语言模型(LLMs)能够进行推理和规划吗?

大语言模型(LLMs),基本上是经过强化训练的 n-gram 模型,它们在网络规模的语言语料库(实际上,可以说是我们文明的知识库)上进行了训练,展现出了一种超乎预期的语言行为,引发了我们的广泛关注。从训练和操作的角度来看,LLMs 可以被认为是一种巨大的、非真实的记忆库,相当于为我们所有人提供了一个外部的系统 1(见图 1)。然而,它们表面上的多功能性让许多研究者好奇,这些模型是否也能在通常需要系

【操作系统】信号Signal超详解|捕捉函数

🔥博客主页: 我要成为C++领域大神🎥系列专栏:【C++核心编程】 【计算机网络】 【Linux编程】 【操作系统】 ❤️感谢大家点赞👍收藏⭐评论✍️ 本博客致力于知识分享,与更多的人进行学习交流 ​ 如何触发信号 信号是Linux下的经典技术,一般操作系统利用信号杀死违规进程,典型进程干预手段,信号除了杀死进程外也可以挂起进程 kill -l 查看系统支持的信号

ABAP怎么把传入的参数刷新到内表里面呢?

1.在执行相关的功能操作之前,优先执行这一段代码,把输入的数据更新入内表里面 DATA: lo_guid TYPE REF TO cl_gui_alv_grid.CALL FUNCTION 'GET_GLOBALS_FROM_SLVC_FULLSCR'IMPORTINGe_grid = lo_guid.CALL METHOD lo_guid->check_changed_data.CALL M

java中查看函数运行时间和cpu运行时间

android开发调查性能问题中有一个现象,函数的运行时间远低于cpu执行时间,因为函数运行期间线程可能包含等待操作。native层可以查看实际的cpu执行时间和函数执行时间。在java中如何实现? 借助AI得到了答案 import java.lang.management.ManagementFactory;import java.lang.management.Threa

js+css二级导航

效果 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Con

人工和AI大语言模型成本对比 ai语音模型

这里既有AI,又有生活大道理,无数渺小的思考填满了一生。 上一专题搭建了一套GMM-HMM系统,来识别连续0123456789的英文语音。 但若不是仅针对数字,而是所有普通词汇,可能达到十几万个词,解码过程将非常复杂,识别结果组合太多,识别结果不会理想。因此只有声学模型是完全不够的,需要引入语言模型来约束识别结果。让“今天天气很好”的概率高于“今天天汽很好”的概率,得到声学模型概率高,又符合表达

SQL Server中,isnull()函数以及null的用法

SQL Serve中的isnull()函数:          isnull(value1,value2)         1、value1与value2的数据类型必须一致。         2、如果value1的值不为null,结果返回value1。         3、如果value1为null,结果返回vaule2的值。vaule2是你设定的值。        如

Windows 可变刷新率是什么?如何开启?

在现代计算设备中,显示屏的刷新率对用户体验起着至关重要的作用。随着显示技术的不断进步,固定刷新率显示器逐渐被支持可变刷新率(Variable Refresh Rate, VRR)技术的显示器所取代。 可变刷新率定义 可变刷新率是什么?可变刷新率(VRR)是一种显示技术,它允许显示器的刷新率动态调整,以匹配显卡输出的帧率。传统的显示器通常具有固定的刷新率(如60Hz、75Hz等),这意味着显示器

C语言 将“China”译成密码

将“China”译成密码,密码规律是:用原来的字母后面的第4个字母代替原来的字母。例如,字母“A”后面的第4个字母是“E”,用“E”代替“A”。因此,“China”应译为“Glmre”。编译程序用付赋初值的方法使c1,c2,c3,c4,c5这五个变量的值分别为“C”,“h”,“i”,“n”,“a”,经过运算,使c1,c2,c3,c4,c5分别变成“G”,“l”,“m”,“r”,“e”。分别用put