首页 > 代码库 > 1013-----C语言----------几个va_宏函数的使用

1013-----C语言----------几个va_宏函数的使用

  最近在看APUE附带的源码时,看到它的错误处理文件中用到了可变参数列表(如下),正好最近老是看到这几个函数在眼前晃悠,所以就做个了断吧。哈哈。

#include "apue.h"#include <errno.h>		/* for definition of errno */#include <stdarg.h>		/* ISO C variable aruments */static void	err_doit(int, int, const char *, va_list);/* * Nonfatal error related to a system call. * Print a message and return. */voiderr_ret(const char *fmt, ...){	va_list		ap;	va_start(ap, fmt);	err_doit(1, errno, fmt, ap);	va_end(ap);}/* * Fatal error related to a system call. * Print a message and terminate. */voiderr_sys(const char *fmt, ...){	va_list		ap;	va_start(ap, fmt);	err_doit(1, errno, fmt, ap);	va_end(ap);	exit(1);}/* * Fatal error unrelated to a system call. * Error code passed as explict parameter. * Print a message and terminate. */voiderr_exit(int error, const char *fmt, ...){	va_list		ap;	va_start(ap, fmt);	err_doit(1, error, fmt, ap);	va_end(ap);	exit(1);}/* * Fatal error related to a system call. * Print a message, dump core, and terminate. */voiderr_dump(const char *fmt, ...){	va_list		ap;	va_start(ap, fmt);	err_doit(1, errno, fmt, ap);	va_end(ap);	abort();		/* dump core and terminate */	exit(1);		/* shouldn‘t get here */}/* * Nonfatal error unrelated to a system call. * Print a message and return. */voiderr_msg(const char *fmt, ...){	va_list		ap;	va_start(ap, fmt);	err_doit(0, 0, fmt, ap);	va_end(ap);}/* * Fatal error unrelated to a system call. * Print a message and terminate. */voiderr_quit(const char *fmt, ...){	va_list		ap;	va_start(ap, fmt);	err_doit(0, 0, fmt, ap);	va_end(ap);	exit(1);}/* * Print a message and return to caller. * Caller specifies "errnoflag". */static voiderr_doit(int errnoflag, int error, const char *fmt, va_list ap){	char	buf[MAXLINE];	vsnprintf(buf, MAXLINE, fmt, ap);	if (errnoflag)		snprintf(buf+strlen(buf), MAXLINE-strlen(buf), ": %s",		  strerror(error));	strcat(buf, "\n");	fflush(stdout);		/* in case stdout and stderr are the same */	fputs(buf, stderr);	fflush(NULL);		/* flushes all stdio output streams */}

1.先重温一下常用的含有可变参数的函数,以格式化输出函数族为例:

#include <stdio.h>int printf(const char *format, ...);                //格式化到标准输出int fprintf(FILE *stream, const char *format, ...);        //格式化到文件int sprintf(char *str, const char *format, ...);        //格式化到字符串int snprintf(char *str, size_t size, const char *format, ...);//规定了格式化字符串的最大长度#include <stdarg.h>int vprintf(const char *format, va_list ap);                 int vfprintf(FILE *stream, const char *format, va_list ap);int vsprintf(char *str, const char *format, va_list ap);int vsnprintf(char *str, size_t size, const char *format, va_list ap);

  这两组函数的功能是一样的,区别是下面使用了va_list类型的变量,也就是说用va_list类型代替了数目不定的参数。第一种用的已经不能再熟了,主要来看第二种的用法。

2.几个va_类宏函数的用法
  2.1 函数定义

#include <stdarg.h>            void va_start(va_list ap, last);       type va_arg(va_list ap, type);       void va_end(va_list ap);       void va_copy(va_list dest, va_list src);

  va_list           //可变参数列表指针类型 ,在x86中定义为 char *;

  va_start(va_list ap, last)  //该函数用来初始化ap,使得ap指向可变参数列表的第一个参数;last变量是可变参数列表前面的那个参数; 

  va_arg(va_list ap, type)    // 返回ap指向的参数的内容,该参数类型为type,ap指向可变参数列表中的下一个参数;

  va_end(va_list ap)         //清空arg;

  va_copy(va_list dest, va_list src) //复制参数列表指针。

  2.2 用法示例,这里要注意使用该函数需要在参数列表中指出参数数目或者约定参数列表结束符,否则将会发生越界错误。

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <stdarg.h>int test(char *fmt, ...);int main(int argc, const char *argv[]){    test("test", "hello", "world","");    return 0;}int test(char *fmt, ...){    va_list ap;    char *temp;    va_start(ap, fmt);    while(1){        temp = va_arg(ap, char *);        if(temp != ""){            printf("%s ", temp);        }        else            break;    }    va_end(ap);    return 0;}

3.宏函数的实现
  通过函数的用法可以看出,这些宏函数的实现关键在于参数列表指针的移动,不同的类型移动的字节数不同,C中定义了这个宏用于判断变量的长度并且该宏函数实现了内存对齐。
#define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )

  宏函数的实现如下:

#define va_start(ap, v) (ap = (va_list)&v + _INTSIZEOF(v) )#define va_arg(ap,t) (*(t *)( (ap += _INTSIZEOF(t)) - _INTSIZEOF(t) )#define va_end(ap) (ap = (va_list)0)

  第二个较难理解,这里需要分解

  (1)ap += _INTSIZEOF(t)     //ap指向下一个参数的地址

  (2)宏函数返回 当期参数 return *(t *)(ap - _INTSIZEOF(t))。

4.调用一个函数时 栈帧的情况
  这里涉及寄存器的使用及部分汇编,待我这几天看一下汇编语言再总结。

  http://blog.chinaunix.net/uid-20718384-id-3418279.html

5.参考
  http://www.360doc.com/content/10/1220/11/1317564_79712393.shtml
  http://www.360doc.com/content/10/1220/11/1317564_79711248.shtml
  http://blog.chinaunix.net/uid-23089249-id-34493.html

1013-----C语言----------几个va_宏函数的使用