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

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

asmlinkage int printk(const char *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.
  */

#ifndef va_arg

#ifndef _VALIST
#define _VALIST
typedef char *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)))
#define va_arg(ap, T) (*(T *)(((ap) += (_bnd (T, _AUPBND))) - (_bnd (T,_ADNBND))))
#define va_end(ap) (void) 0
#define va_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(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] = '\0';
    va_end(args);

printf(str);        
}

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

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

void printascii(const char *); 
void printhex(unsigned long value, int nbytes); 
void printhex2(unsigned char value); 
void printhex4(unsigned short value); 
void printhex8(unsigned long value);

这些函数都是用汇编实现的,并直接从低层操作s3c2410的串口,并显示信息。因此不需要等到console_init后就可以显示信息。
    为了使用这些函数,需要特性的内核选项支持:make menuconfig 
      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里:

extern void printascii(const char *);

static void debug(const char *fmt, ...)
{
    va_list va;                
    char buff[256];

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

printascii(buff);
}

转自:http://blog.chinaunix.net/uid-23089249-id-34493.html

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

  1. c语言va_start函数,va_start和va_end,以及c语言中的可变参数原理

    FROM:http://www.cnblogs.com/hanyonglu/archive/2011/05/07/2039916.html 本文主要介绍va_start和va_end的使用及原理. 在 ...

  2. c语言 函数多个参数,C语言函数可变参数

    C语言函数可变参数教程 可变参数的函数必须至少有一个强制参数,可选参数的类型可以变化.可选参数的数量由强制参数的值决定,或由用来定义可选参数列表的特殊值决定. 对于每一个强制参数来说,函数头部都会显示 ...

  3. #{}不自动改参数类型_【Just For Fun】C - 可变参数函数、可变参数宏 __VA_ARGS__、额外的逗号

    [Just For Fun] 本系列纯粹娱乐.研究用.一些旁门左道的东西. 事实上可能完全没用. (๑•̀ω•́๑) 對於可变参数函数.可变参数宏 __VA_ARGS__ , 我曾經有在另一些地方寫過 ...

  4. c语言理解参数,c语言中对可变参数列表的简单理解

    函数原型中一般情况下参数的数目是固定的,但是如果想在不同的时候接收不定数目的参数时该怎么办呢?c语言提供了可变参数列表来实现. 可变参数列表是通过宏来实现的,这些宏定义在stdarg.h的头文件中.头 ...

  5. C语言 函数不定长参数 ##__VA_ARGS__经典案例 - C语言零基础入门教程

    目录 一.##__VA_ARGS__简介 二.##__VA_ARGS__经典案例 三.猜你喜欢 零基础 C/C++ 学习路线推荐 : C/C++ 学习目录 >> C 语言基础入门 一.## ...

  6. C语言 函数不定长参数 - C语言零基础入门教程

    目录 一.前言 二.函数不定长参数简介 1.va_start 2.va_arg 3.va_end 三.自定义不定长参数的函数 1.va_start/va_arg/va_end 案例一 2.va_sta ...

  7. Python 函数的可变参数(*paramter与**paramter)的使用

    Python 函数的可变参数主要有 *paramter与**paramter 可变参数主要有 *paramter的作用 接受任意多个实际参数并放到一个元组中 def people(*people):f ...

  8. Python 函数的可变参数、切片、迭代和列表生成式

    1. 函数的可变参数 def fun(*args):print(args) 让一个函数能接受任意个参数,可以定义一个可变参数.可变参数的名字前面有个 * 号,我们可以传入0个.1个或多个参数给可变参数 ...

  9. C语言 函数缺省参数 - C语言零基础入门教程

    目录 一.函数简介 1.函数声明 2.函数定义 3.函数调用 4.函数形参和实参 二.函数缺省参数 1.函数全缺省参数 2.函数半缺省参数 三.注意事项 四.猜你喜欢 零基础 C/C++ 学习路线推荐 ...

  10. JS与PHP向函数传递可变参数的区别

    # JS 调用函数传递可变参数的方法 <script> function test() { for(var i =0;i < arguments.length; i++) { ale ...

最新文章

  1. 最常见企业管理SAAS软件有哪些?要解决哪些问题?
  2. springmvc是如何和前端页面联系起来的
  3. 常见的HTML元素及常见检查点
  4. challenging and foundational
  5. sysbench测试mysql性能(TPS、QPS、IOPS)(重要)
  6. php文件多上传文件,php文件上传(多文件上传)
  7. django orm插入一条_如何通过django的ORM远程发布文章?
  8. XSS-Payloads集合
  9. Hyper-V 3.0网络虚拟化PART 3:内部交换机
  10. java方法重写和super关键字
  11. UnauthorizedAccessException Invaild cross-thread access
  12. 企业销售管理现状分析与解决思路(转)
  13. pandas的基本函数
  14. join原理、join算法
  15. session的销毁方式
  16. win10打开文件夹卡顿
  17. Oracle 18c十大新特性
  18. 群晖NAS教程(二十三)、利用Docker安装mysql8,并使用ipv6和域名访问
  19. VSLAM之边缘化 Marginalization 和 FEJ (First Estimated Jocobian)
  20. js 获取指定日期的前几天日期或后几天日期

热门文章

  1. android 动画闪屏问题,Android中闪屏实现方法小结(普通闪屏、倒计时闪屏、倒计时+动画...
  2. 百度应用开放平台简介
  3. 闲人闲谈PS之二十二——WBS结算规则批量维护函数
  4. Android TextView显示表情、标签、超链接
  5. python人名统计_「姓名分析」Python|美国婴儿姓名分析 - seo实验室
  6. JSON.stringify(value[, replacer [, space]])
  7. maven 为html赋版本号,maven-replacer-plugin 静态资源版本号解决方案(css/js等)
  8. 使用kmean进行图像分割 使用CRFs进行分割后处理
  9. [凸优化]1-凸集和凸函数
  10. mysql物理备份恢复搭建从库_RDS FOR MYSQL 各版本利用物理备份搭建从库方法