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

转载 2015年11月19日 14:47:05
说到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) (*(*)(((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 (< 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);
}

开启lowlevel debug。使用printascii()函数。(printacii是汇编语言写成,在kernel/arch/arm/kernel/debug.S有定义)
 76 #make menuconfig ARCH=arm
 77 选择AM33XX UART1,也就是UART0.调试串口。具体如下:
 78 Symbol: DEBUG_AM33XXUART1
 79      -> Kernel hacking
 80        -> Kernel low-level debugging functions (read help!)
 81          -> Kernel low-level debugging port
 82 使用方法如:
 83 extern void printascii(char *);
 84  printascii("sssssssssssssssssssssssssssssssssssssssssssssssss\n\n");
 85 高级用法:
 86 extern void printascii(char *);
 87  void ymj_debug(const char *fmt, ...)
 88  {
 89     va_list va;
 90     char buff[256];
 91     va_start(va, fmt);
 98     vsprintf(buff, fmt, va);
 99     va_end(va);
100     printascii(buff);
101  }
102  ymj_debug("dddddddddddddddddddddd %s  %d\n\n",__FILE__,__LINE__);

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

C语言可变参简介     我们在C语言编程中会遇到一些参数个数可变的函数,例如printf()这个函数,它的定义是这样的:       int printf( const char* fo...
  • edonlii
  • edonlii
  • 2013年01月13日 11:31
  • 44540

C 中解决可变参数的几组宏 va_start、va_arg、va_end等

在C语言中有时候会用到可变参数,利用va_list这组宏就可以解决: #include void va_start(va_list ap, last); type v...
  • hb255255
  • hb255255
  • 2013年07月25日 18:31
  • 888

va_list & va_start & va_arg & va_end

va_list 属于变量 而 va_start & va_arg & va_end  C语言中解决变参问题的一组宏。头文件来自stdarg.h。 查看linux系统源码方式我一般用locate std...
  • tskyming
  • tskyming
  • 2014年07月04日 10:47
  • 1335

va_list,va_start,va_end分析

void dprintf( char * format, ...){    static char buf[1024];    va_list args;    // typedef char *  ...
  • cleverwyq
  • cleverwyq
  • 2007年05月25日 17:53
  • 2535

va_start和va_end使用详解

记录一下我的写法: char log_temp[20480]; memset(log_temp,0,sizeof(log_temp)); va_list args; va_start(args, l...
  • wo1017
  • wo1017
  • 2016年01月31日 11:07
  • 2346

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

C语言可变参简介     我们在C语言编程中会遇到一些参数个数可变的函数,例如printf()这个函数,它的定义是这样的:       int printf( const char* fo...
  • edonlii
  • edonlii
  • 2013年01月13日 11:31
  • 44540

va_start(),va_arg(),va_end()

(一)写一个简单的可变参数的C函数 下面我们来探讨如何写一个简单的可变参数的C函数.写可变参数的 C函数要在程序中用到以下这些宏: void va_start( va_list arg_pt...
  • holandstone
  • holandstone
  • 2012年12月07日 11:32
  • 2751

代码调试利用va_list,va_start,snprintf,va_end

在linux下的开源项目里,特别是用C/C++编写的程序,一般都有debug.h、debug.c文件,这个文件里的函数格式都差不多,格式一般如下:DebugMessagePrint(char *fmt...
  • guneta
  • guneta
  • 2009年05月15日 21:46
  • 286

利用va_list,vfprintf等定义自己的文件输出函数

最近在做项目的时候,经常需要
  • tongsean
  • tongsean
  • 2014年11月07日 23:05
  • 2120

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

说到C语言函数可变参数,我们最先想到的可能就是printf、scanf、printk了。在Linux-2.6.24.7内核源码里,printk函数原型如下: asmlinka...
  • liu5320102
  • liu5320102
  • 2015年08月11日 11:19
  • 1046
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:C语言函数之可变参数原理:va_start、va_arg及va_end !!!!!!和printascii在kernel启动前的应用
举报原因:
原因补充:

(最多只允许输入30个字)