百篇大计敬本年之C语言巅峰之道《三》 —— va_start、va_end、va_arg、va_copy可变参数...

在 Linux 编程中经常用到 printf 函数,其函数定义如下:

/* Write formatted output to stdout.

   This function is a possible cancellation point and therefore not
   marked with __THROW.  */
extern int printf (__const char *__restrict __format, ...);

这是最典型的C标准 —— 未知个数参数传递的函数模型,"printf" 传递的参数个数可以自由控制:

int  sum = 100;
char p[] = "Test";

printf("%s", p);
printf("%s %d", p, sum);

原理解析

1、函数参数的内存存放格式。

    函数参数是以数据结构的方式:栈的形式存取,从右往左进栈,即最后一个参数先进栈,如:

int func(int m, char n, double k);

    参数 "k" 先进栈,然后是 "n" -> "m",即从栈底往上次序是 k->n->m ,所以,栈底是高地址,栈顶是低地址。也符合栈的后进先出的概念。

2、调用。

    调用参数时从栈顶开始一直寻址至栈底,即次序为 m->n->k,因此,理论上说,只要知道 m 的地址,顺手牵羊,哦不,顺藤摸瓜,即可寻到其他函数参数。

stdarg.h 中宏定义的原型

#define va_start(v,l)	__builtin_va_start(v,l)
#define va_end(v)	    __builtin_va_end(v)
#define va_arg(v,l)	    __builtin_va_arg(v,l)
#define va_copy(d,s)	__builtin_va_copy(d,s)

等同于

va_start(va_list p, fmt);
va_end(va_list p);
type va_arg(va_list p, type);
va_copy(va_list dest, va_list source);

假如定义一个函数

int printb(char *fmt, ...);

"va_list" 定义的是一个指向参数列表的指针(类型可能是"char *"或者"void *"),"fmt" 指向输入参数的第一个参数,此参数应必须存在,否则无法获取后面的参数。"type"是数据类型,可取值 "int"、"char *"类型(其他类型尚需实测)。

下面是一个简单的例子

#include <stdio.h>
#include <stdarg.h>

 static int MAX_ARGS=4;

int printb(char *fmt, ...)
{
    char buf[1024];
    char *bb=NULL;
    va_list ap;
    int argno = 0;

    va_start(ap, fmt);
    vsnprintf(buf, sizeof(buf), fmt, ap);
    while(fmt!=0 && argno<MAX_ARGS)
    {
        bb = va_arg(ap, char*);
        printf("bb=%s ap=%p \n",bb, ap);
        argno ++;
    }
    va_end(ap);

    printf("\nThere are your enter strings:%s\n", buf);
    
    return 0;
}

int main()
{
    printb("Bingwu  %s %s %s %s", "WHAT", __FILE__, __FUNCTION__, "Oh no");

    return 0;
}

    特别注意,在使用 "va_arg" 宏时一定要留心输入参数的类型,如上述例子中把main函数改成

int main()
{
    printb("Bingwu  %s %s %d %s", "WHAT", __FILE__, __LINE__, "Oh no");

    return 0;
}

那么整个程序的运行将会产生段错误!因为 "va_arg" 指定了 "char *" 类型,那么将无法容忍其他类型的输入!所以,除非是刚性需求,一般使用只用如下模型即可

 va_start(ap, fmt);
 vsnprintf(buf, sizeof(buf), fmt, ap);
 va_end(ap);

va_start 与 va_end 应总是成对出现,方能符合 初始化->其他调用->去初始化 的安全性原则。

转载于:https://my.oschina.net/bingwu/blog/787955

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值