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_start、va_arg及va_end

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

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

本文转载自:http://blog.chinaunix.net/uid-23089249-id-34493.html    作者:lingdxuyan 说到C语言函数可变参数,我们最...
  • vgxpm
  • vgxpm
  • 2015-07-15 22:57
  • 390

C语言可变参数函数及三个宏va_start、va_arg和va_end的使用

一、可变参数函数的实例大家熟知的printf()函数声明如下:    int  printf(const char * format, ...);它除了有一个参数format固定以外,后面跟的参数的个...
  • c80486
  • c80486
  • 2011-06-25 14:51
  • 1442

C/C++中用va_start/va_arg/va_end实现可变参数函数的原理与实例详解

在C/C++中,我们经常会用到可变参数的函数(比如printf/snprintf等),本篇笔记旨讲解通过va_start/va_arg/va_end这簇宏来实现可变参数函数的原理,并在文末给出简单的实...
  • slvher
  • slvher
  • 2013-08-10 17:51
  • 4756

C/C++中用va_start/va_arg/va_end实现可变参数函数的原理与实例详解 .

原文链接:http://blog.csdn.net/slvher/article/details/9881171   在C/C++中,我们经常会用到可变参数的函数(比如printf/snprintf等...

深入C语言可变参数(va_arg,va_list,va_start,va_end,_INTSIZEOF)

一、什么是可变参数          在C语言编程中有时会遇到一些参数个数可变的函数,例如printf(),scanf()函数,其函数原型为:  int printf(const char* ...

C语言中的可变参数:va_list ,va_start,va_arg,va_end

C语言中有些函数使用可变参数,比如常见的int printf( const char* format, ...),第一个参数format是固定的,其余的参数的个数和类型都不固定。但C又无法用面相对象的...

C语言中可变参数va_list/va_start/value_arg/va_end的理解

va_list/va_start/va_arg/va_end这几个宏,都是用于函数的可变参数的。 我们来看看在vs2008中,它们是怎么定义的: 1: ///stdarg.h 2: ...

《C语言接口与实现》实验——可变参数表的使用(va_list, va_start, va_arg, va_end)

《C语言接口与实现》作为接口库,源文件中大量使用了可变参数表,这些到底是怎么使用的?先来看这几个例子,基本明白了可变参数表使用。后面部分从网上整理了原理: 源程序: #include #incl...

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

C语言可变参简介     我们在C语言编程中会遇到一些参数个数可变的函数,例如printf()这个函数,它的定义是这样的:       int printf( const char* fo...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

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