说起c语言函数的可变参数,可能最先想到的就是printf、scanf、printk。 在Linux-2.6.24.7内核源中,printf函数的原型为以下:
asmlinkageintprintk (const char * fmt,) )。
asmlinkage表示通过堆栈传递参数。 gcc编译器在汇编过程中调用c语言函数时传递参数有两种方法:通过堆栈和通过寄存器。 默认情况下使用寄存器。 如果要在程序集期间调用c语言函数并通过堆栈传递参数,请在定义的c函数之前添加宏asmlinkage。
从printk函数原型中可以看到,除了printk接收一个固定参数fmt外,后面的参数用.表示。 在C/C语言中,表示可以接收常数的参数(0以上的参数)。 那么,printk是如何支持可变参数的呢?
首先,我们来看几个宏。 va_list、va_start、va_arg和va_end(va应表示variable )。 在Linux-2.6.24.7内核源中,其定义(内核中的定义与c语言库的定义类似
/*
* uselocaldefinitionsofclibrarymacrosandfunctions
* note : thefunctionimplementationsmaynotbeasefficient
* asaninlineorassemblycodeimplementationprovidedbya
*国家库。
*/
#ifndefva_arg
#ifndef_VALIST
#define_VALIST
typedefchar*va_list;
#endif/* _VALIST */
/*
*存储对齐属性
*/
# define _ aup bnd (sizeof (acpi _ native _ int (-1 ) ) ) ) ) )。) ) ) )。
# define _ adnb nd (sizeof (acpi _ native _ int (-1 ) ) ) ) ) ) ) ) ) ) ) ) ) )。
/*
* variableargumentlistmacrodefinitions
*/
#define_bnd(x,bnd ) ) ) (sizeof(X ) (bnd ) ) (~(bnd ) )
#defineva_arg(AP,t ) ) (t* ) ) ) ) AP=(bnd ) t,_AUPBND ) ) ) ) )、) bnd,) 652
#defineva_end(AP ) ) void ) 0
#defineva_start(AP,a ) (void ) ) (AP=) ) ) 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] = ' ';
for (start = str, end = str + strlen(str) - 1;
start < end; start++, end--)
{
temp = *start;
*start = *end;
*end = temp;
}
return str;
}
void print(const char *fmt, ...)
{
char str[100];
unsigned int 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] = ' ';
va_end(args);
printf(str);
}
int main()
{
print("Version: %d; Modifier: %sn", -958, "lingd");
return 0;
}
文章来源:https://blog.csdn.net/liu5320102/article/details/47417517