C语言函数之可变参数原理:va_start、va_arg及va_end

Vue集成天地图:从定位到交互绘图的完整实践指南 本文详细介绍了如何在Vue项目中集成天地图API,从基础配置到高级功能实现,包括定位、标点、画线等交互绘图功能。通过实战代码示例和性能优化技巧,帮助开发者快速掌握Vue与天地图的结合应用,提升地图开发效率。 阅读详情

说到C语言函数可变参数,我们最先想到的可能就是printf、scanf、printk了。在Linux-2.6.24.7内核源码里,printk函数原型如下:

asmlinkage int printk(constchar*fmt,...)

asmlinkage表示通过堆栈传递参数。gcc编译器在汇编过程中调用c语言函数时传递参数有两种方法:一种是通过堆栈,另一种是通过寄存器。缺省时采用寄存器,假如你要在你的汇编过程中调用c语言函数,并且想通过堆栈传递参数,你定义的c函数时要在函数前加上宏asmlinkage。

从printk函数原型可知,printk除了接收一个固定参数fmt外,后面的参数用...表示。在C/C++语言中,...表示可以接收定数量的参数(0或0以上个参数)。那么printk到底是怎么支持可变参数的呢?

我们先来看几个宏:va_list、va_start、va_arg及va_end(va的意思应该是variable),在Linux-2.6.24.7内核源码里,其定义(内核里的定义与C语言库的定义是类似的)如下

/*
* Use local definitions of C library macros and functions
* NOTE: The function implementations may not be as efficient
* as an inline or assembly code implementation provided by a
* native C library.
*/

#ifndefva_arg

#ifndef _VALIST
#define _VALIST
typedefchar*va_list;
#endif/* _VALIST */

/*
* Storage alignment properties
*/
#define _AUPBND (sizeof(acpi_native_int)- 1)
#define _ADNBND (sizeof(acpi_native_int)- 1)

/*
* Variable argument list macro definitions
*/
#define _bnd(X, bnd)(((sizeof(X))+(bnd))&(~(bnd)))
#defineva_arg(ap, T)(*(T *)(((ap)+=(_bnd (T, _AUPBND)))-(_bnd (T,_ADNBND))))
#defineva_end(ap)(void) 0
#defineva_start(ap, A)(void)((ap)=(((char*)&(A))+(_bnd (A,_AUPBND))))

#endif/* va_arg */

1、va_list

va_list表示可变参数列表类型,实际上就是一个char指针

2、va_start

va_start用于获取函数参数列表中可变参数的首指针(获取函数可变参数列表)
* 输出参数ap(类型为va_list): 用于保存函数参数列表中可变参数的首指针(即,可变参数列表)
* 输入参数A: 为函数参数列表中最后一个固定参数

3、va_arg

va_arg用于获取当前ap所指的可变参数并将并将ap指针移向下一可变参数
*
输入参数ap(类型为va_list): 可变参数列表,指向当前正要处理的可变参数
*
输入参数T: 正要处理的可变参数的类型
* 返回值: 当前可变参数的值

在C/C++中,默认调用方式_cdecl是由调用者管理参数入栈操作,且入栈顺序为从右至左,入栈方向为从高地址到低地址。因此,第1个到第n个参数被放在地址递增的堆栈里。所以,函数参数列表中最后一个固定参数的地址加上第一个可变参数对其的偏移量就是函数的可变参数列表了(va_start的实现);当前可变参数的地址加上下一可变参数对其的偏移量的就是下一可变参数的地址了(va_arg的实现)。这里提到的偏移量并不一定等于参数所占的字节数,而是为参数所占的字节数再扩展为机器字长(acpi_native_int)倍数后所占的字节数(因为入栈操作针对的是一个机器字),这也就是为什么_bnd那么定义的原因。

4、va_end

va_end用于结束对可变参数的处理。实际上,va_end被定义为空.它只是为实现与va_start配对(实现代码对称和"代码自注释"功能)

对可变参数列表的处理过程一般为:

1、用va_list定义一个可变参数列表

2、用va_start获取函数可变参数列表

3、用va_arg循环处理可变参数列表中的各个可变参数

4、用va_end结束对可变参数列表的处理

下面是一个例子:

#include<stdio.h>
#include<stdarg.h> /* 使用va_list、va_start等必须包含的头文件 */
#include<string.h>

/* linux C没有itoa函数,所以要自己写 */
char*itoa(int i,char*str)
{
int mod,div=fabs(i), index = 0;
char*start,*end, temp;

do
{
mod =div% 10;
str[index++]='0'+ mod;
div=div/ 10;
}while(div!= 0);

if(i < 0)
str[index++]='-';

str[index]='\0';

for(start = str, end = str +strlen(str)- 1;
start < end; start++, end--)
{
temp =*start;
*start =*end;
*end = temp;
}

return str;
}

void print(constchar*fmt,...)
{
char str[100];
unsignedint len, i, index;
int iTemp;
char*strTemp;
va_list args;

va_start(args, fmt);
len =strlen(fmt);
for(i=0, index=0; i<len; i++)
{
if(fmt[i]!='%')/* 非格式化参数 */
{
str[index++]= fmt[i];
}
else/* 格式化参数 */
{
switch(fmt[i+1])
{
case'd':/* 整型 */
case'D':
iTemp =va_arg(args,int);
strTemp = itoa(iTemp, str+index);
index +=strlen(strTemp);
i++;
break;
case's':/* 字符串 */
case'S':
strTemp =va_arg(args,char*);
strcpy(str + index, strTemp);
index +=strlen(strTemp);
i++;
break;
default:
str[index++]= fmt[i];
}
}
}
str[index]='\0';
va_end(args);

printf(str);
}

int main()
{
print("Version: %d; Modifier: %s\n",-958,"lingd");
return 0;
}

另一个比较实用的例子:printk只能在内核初始化完控制台(console_init)后才能使用。因此,在Linux内核初始化控制台前,只能使用其它函数来打印内核消息。这些函数有:

void printascii(constchar*);
void printhex(unsignedlong value,int nbytes);
void printhex2(unsignedchar value);
void printhex4(unsignedshort value);
void printhex8(unsignedlong value);

这些函数都是用汇编实现的,并直接从低层操作s3c2410的串口,并显示信息。因此不需要等到console_init后就可以显示信息。
为了使用这些函数,需要特性的内核选项支持:makemenuconfig
Kernel hack ->
[*]Kernel low-level debugging functions
[*]Kernel low-level debugging messages via S3C UART
(0)S3C UART to use for low-level debug

但是,这些函数并没有printk功能那么强大,支持可变参数,支持格式化字符串。为了在Linux内核初始化控制台前,能使用了一个类似于printk的函数,我们将printascii函数封装到新函数debug里:

externvoid printascii(constchar*);

staticvoid debug(constchar*fmt,...)
{
va_list va;
char buff[256];

va_start(va, fmt);

/* 函数vsprintf:用于输出格式化字符串到缓冲区
* 输出参数buff:用于保存结果的缓冲区
* 输入参数fmt: 格式化字符串
* 输入参数va: 可变参数列表
* 返回值: 实际输出到缓冲区的字符数
*/
vsprintf(buff, fmt, va);
va_end(va);

printascii(buff);
}

 


Gephi新手必看:如何用Excel整理CSV数据快速生成网络图(附模板) 本文为Gephi新手提供了一份从Excel数据整理到生成专业网络图的完整指南。详细讲解了如何构建规范的节点表和边表CSV文件,并导入Gephi进行可视化分析。通过实战案例,帮助读者快速掌握利用Excel整理数据、规避常见错误,并运用布局算法与视觉编码,将复杂关系数据转化为直观的网络洞察。 阅读详情

相关推荐

海波龙(Oracle Hyperion)单点登陆问题记录

Oracle海波龙套件已经多年未更新,按Oracle官方文档实施单点登陆启用之后,SmartView工具通过专用链接方式无法登陆系统。 在抓包逆向分析之后 定位到HspSmartViewServlet类在执行以下代码时获得的ssotoken无效导致,在某大厂工作的时候曾通过在filter中截获请求的方式植入Attribute,保留账号密码并在单点程序中处理实现登陆。 但在完美世界的环境中...

cainiaoEzu的专栏 1970

【C语言入门】可变参数函数:stdarg.h头文件(va_list, va_start, va_arg, va_end)

在C语言中,可变参数函数通过stdarg.h头文件提供的一组宏(va_list、va_start、va_arg、va_end)实现,用于处理参数数量不确定的场景。va_list用于声明参数指针,va_start初始化指针,va_arg读取参数并移动指针,va_end清理指针。可变参数函数广泛应用于打印、统计、日志记录等场景,但使用时需明确参数数量和类型,避免类型错误和资源泄漏。尽管存在类型不安全的局限性,stdarg.h在性能敏感或需要兼容C标准的场景中仍是不可替代的工具。

Mr_-G,一名勤奋底层软件开发工程师的博客 1630

光谱分布、光谱辐射通量密度与不同时间段分布光谱(图示)

1、光谱分布图 2 太阳辐射能量图 3、不同时间段的太阳分布光谱图 4、不同波长的光的能量分布主要区域 5、不同波段的使用场景

maopig的专栏 2万+

C/C++要点全掌握(六)——变长参数

上接C/C++要点全掌握(五)——mutable、volatile     14、变长参数          设计一个参数个数可变、参数类型不定的函数是可能的,最常见的例子是printf函数、scanf函数和高级语言的Format函数。在C/C++中,为了通知编译器函数的参数个数和类型可变(即是不定的、未知的),就必须以三个点结束该函数的声明。 // printf函数的声明

TU2的专栏 1万+

Frida三层次钩子快速逆向VMP+OLLVM加固APK

VMP(虚拟机保护)和OLLVM(控制流平坦化)是Android应用常见的双重混淆技术,其核心原理是将Java逻辑虚拟化、Native代码控制流打散,从而阻断传统静态分析与简单Hook。但任何混淆都必须在CPU上执行,因此必然暴露JNI注册行为、ABI参数传递规律及系统调用痕迹。基于此,Frida可通过跨层联动——从JNI_OnLoad捕获Native库加载、在VMP解释器入口截获字节码上下文、于关键系统调用(如read/gettimeofday)反向追踪数据流——构建可观测、可复现的动态分析链。该方法无需

weixin_30888413的博客 1002

Delphi 13 Beta初体验:手把手教你安装RAD Studio Ganymede,并实测新条件运算符

本文详细介绍了Delphi 13 Beta(代号Ganymede)的安装步骤和新特性,重点解析了新增的条件运算符语法。通过实测对比,展示了64位IDE的性能提升和新语法在代码简洁性上的优势,为开发者提供了从安装到实战的完整指南。

weixin_30399821的博客 205

arg是Linux函数,Linux下函数可变参数va_arg_##__VA_ARGS__宏

va_list//compile:gcc va_arg.c//run:./a.out//Notes:如果遇到获取char,type用int,如果该用char会提示...传递时被提升为int。此处使用的编译器是Linux下的gcc#include#include#define filename "filemark"//***************************************#d...

weixin_32926649的博客 775

单细胞测序中pseudo bulk差异分析的实战指南与审稿人应对策略

本文提供了单细胞测序中pseudo bulk差异分析的完整实战指南,旨在应对审稿人对样本量统计效力的质疑。文章详细解析了从数据聚合、使用DESeq2进行差异分析,到与单细胞水平结果进行交叉验证的全流程,并给出了应对常见问题与撰写回复的策略,帮助研究者提升结论的稳健性与说服力。

weixin_29214199的博客 820

C语言可变参数函数_初探

一、什么是可变参数函数 C语言允许定义参数数量可变的函数,这称为可变参数函数(variadic function)。这种函数需要固定数量的强制参数,后面是数量可变的可选参数。 其中,强制参数必须至少一个,可选参数数量可变,类型可变,可选参数的数量由强制参数的值决定,或由用来定义可选参数列表的特殊值决定。 其实我们早就接触过可变参数函数了,C 语言中最常用的可变参数函数例子是 printf()和 s...

how的博客 8346

MiniMax H3本地部署与ComfyUI集成实战:从环境配置到提示词优化

文本到视频生成是AI内容创作领域的关键技术,它基于扩散模型等深度学习原理,将自然语言描述转化为连贯的动态视觉序列。这项技术的核心价值在于大幅降低了视频制作的门槛,为短视频创作、概念可视化、教育内容生成等场景提供了高效工具。在实际工程实践中,成功部署和运行此类模型,如近期备受关注的MiniMax H3,需要精准处理环境配置、显存优化和提示词工程等环节。其中,解决“CUDA error: no kernel image is available”等版本兼容性问题,以及针对“8G显存”等有限资源进行参数调优,是确

weixin_33674976的博客 478

6.3 节深度拆解:Hermes Agent 多 Agent 协同执行链路的 4 层设计逻辑

拆解 Hermes 多 Agent 协同的 4 层工程化设计(协议层、编排层、执行层、适配层),聚焦协议层的强约束消息 Schema 如何解决 Agent 间语义不一致问题,并提供 `soul.md` 配置、GLM-4.7Flash 结构化输出验证及 v0.13.0/v0.14.0 版本实操避坑指南

weixin_42520239的博客 122

C语言函数之可变参数原理:va_start、va_arg及va_end !!!!!!和printascii在kernel启动前的应用

说到C语言函数可变参数,我们最先想到的可能就是printf、scanf、printk了。在Linux-2.6.24.7内核源码里,printk函数原型如下: asmlinkage int printk(const char *fmt, ...)       asmlinkage表示通过堆栈传递参数。gcc编译器在汇编过程中调用c语言函数时传

yyyyyyyyyywwwwwwwwww的专栏 1255

LSTM+Transformer混合模型时间序列预测实战教学

LSTM+Transformer混合模型时间序列预测实战教学 毕业设计

8219

C语言中的可变参数

总结:读取可变参数的过程其实就是在堆栈中,使用指针,遍历堆栈段中的参数列表,从低地址到高地址一个一个地把参数内容读出来的过程·(因为入栈是从高地址向低地址入栈,出栈就从低地址向高地址出栈,先入后出)

weixin_52243202的博客 881

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

C语言函数参数中的三个点(三点 “...”)是干什么用的?(可变参数)<stdarg.h>、va_start 宏、va_arg 宏、va_end 宏

在C语言中,有这样的一类函数:函数的参数个数是不确定的,动态变化的。比如我们经常用到的 prinf, sprinf 等等,这与我们平时定义的函数有些不同,它们便是可变参数函数,我们也可以自定义这类函数,这篇文章讲解C语言中的可变参数函数相关的内容。),省略号之前的那个参数是 int,代表了要传递的可变参数的总数。因为不可变参数 n 的地址和类型都能够得到,因此只要将这个指针(将它的地址赋给一个相同类型的指针)加 1 就能得到可变参数 1 的指针,通过这个宏, va_list 的指针就指向第一个可变参数。..

Dontla的博客 6183

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

C语言中可变参数的用法——va_list、va_start、va_arg、va_end参数定义

C语言可变参简介     我们在C语言编程中会遇到一些参数个数可变的函数,例如printf()这个函数,它的定义是这样的:       int printf( const char* format, ...);   它除了有一个参数format固定以外,后面跟的参数的个数和类型是可变的,例如我们可以有以下不同的调用方法:        printf("%d",i);

AddyLee的专栏 7万+

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

C语言可变参数函数的原理和实现,及如何编写自定义可变参数函数

C语言中的可变参数函数是一种可以接受不定数量和类型的参数的函数,它可以用来实现一些灵活和通用的功能。本文介绍了C语言中的可变参数函数的原理和实现,以及如何编写自定义的可变参数函数。

极客代码 5305

list初始化赋值_va_list、va_start和va_end使用

va_list、va_start和va_end使用我们知道va_start,va_arg,va_end是在stdarg.h中被定义成宏的,由于1、硬件平台的不同 2、编译器的不同,所以定义的宏也有所不同。在ANSI C中,这些宏的定义位于stdarg.h中,典型的实现如下:typedef char *va_list;va_start宏,获取可变参数列表的第一个参数的地址(list是类型为va_li...

weixin_39836063的博客 1415

var_start,var_arg,var_end用法

var_start,var_arg,var_end用法 var_start,var_arg,var_end用法(一)一个可变参数函数的例子下面我们来探讨如何写一个简单的可变参数的C函数。写可变参数的 C函数要在程序中用到以下这些宏:void va_start( va_list arg_ptr, prev_param );type va_arg( va_list arg_ptr, type ); v

mindva的专栏 5653

C语言——可变参数

目录: 一:定义 二:类型【举例】 三:解析 四:步骤【五步】 五:可变参数的工作原理 六:补充几个宏 1.va_list: 2.va_start: 3.va_arg: 4.va_end: 5.va: 实例1: 实例2: 一:定义 函数带有可变数量的参数,而不是预定义数量的参数 允许您定义一个函数,能根据具体的需求接受可变数量的参数 二:类型【举例】 ...

刘鑫磊 2989
上一篇: CPSR 和 SPSR
下一篇: Linux C中内联汇编的语法格式及使用方法(Inline Assembly in Linux C)
LinuxWorking
博客等级 码龄10年 24粉丝 32原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值