您的位置:首页 > 其它

用可变参数扩展printf

2017-06-16 16:51 162 查看
VA_LIST 是在C语言中解决变参问题的一组宏,所在头文件
#include <stdarg.h>
,用于获取不确定个数的参数。首先,我们来看看C语言中是如何定义。先看其中比较简单的一种定义,分析完一种,其他实现也可触类旁通。

va_list,定义为字符串指针:

typedef char * va_list;


VA_START宏,获取可变参数列表的第一个参数的地址(ap是类型为va_list的指针,v是可变参数最左边的参数):

#define va_start(ap,v) ( ap = (va_list)&v + 4 )


因为第一个参数是固定的,第一个参数的地址加上4(一个地址的长度),就等于第二个参数的地址。如果要深究,就要研究C语言参数入栈相关知识,参数压栈的方向是从右往左,这里简单提下。

VA_ARG宏,获取可变参数的当前参数,返回指定类型并将指针指向下一参数,实际应用中经常用格式化函数vsprintf代替VA_ARG宏。

VA_END宏,清空va_list可变参数列表:

#define va_end(ap) ( ap = (va_list)0 )


下面展示一个简单的程序流程

char buffer[80] = {0};
int cnt;
va_list argptr; //初始化va_list变量argptr
va_start(argptr, fmt); //获取可变参数列表的第一个参数的地址argptr
cnt = vsprintf(buffer, fmt, argptr);//格式化输出数组buffer
va_end(argptr);//清空va_list可变参数列表


有了上面基础知识,再来看看printf的一种实现

int printf(const char *fmt, ...)
{
int i;
char buf[256];
va_list arg = (va_list)((char*)(&fmt) + 4);//
i = vsprintf(buf, fmt, arg);
write(buf, i);
return i;
}


从上面的程序中可以看出,buf保存着格式化后的数据,系统再调用write函数输出到某一个地方。其实跟我们上一个例子非常相似,不同之处是输出到哪里。我们在调试程序的时候,需要用打印log出来帮我们解决bug。有了上面的知识,我们可以打造一个方便自己调试程序的函数。如下面的例子。

void printf_my_log(char *file, int line, const char *func, char *fmt, ...)
{
char argvStr[1024] = {0};
va_list argptr;
memset(&argptr, 0, sizeof(va_list));
printf("[%s(Line=%d):%s]",file,line,func);
va_start (argptr, fmt);
vsprintf(argvStr, fmt, argptr);
va_end (argptr);
printf("%s\n", argvStr);
}


因为实际项目是很庞大的,我们经常需要定位到哪个文件,哪个函数,哪一行出问题了。所以,我们可以用
__FILE__,__LINE__,__FUNCTION__
这几个宏来简单扩展一下printf的功能。像上面的例子,我们可以这样调用:

printf_my_log(__FILE__,__LINE__,__FUNCTION__,"%d %f %s", a,b,c);


既然讨论到这里了,我们再试试看怎么把log按照你的意图在终端显示出来。在linux系统中,我们可以通过设置printk的打印级别来控制log,在linux应用程序中,我们又是怎么实现类似的机制呢?

好,下面来讨论分析一下如何在应用中控制log的输出。

我们假设一个情景,一个长期运行的Linux程序,想在不退出运行的情况下,通过某种机制,可以让程序知道要不要打印出log

最简单的实现方式可以这样:

1、创建一个文件,写进标志位

2、然后每次要打印log之前先读取这个文件,按照标志位是什么来决定要不要打印log

这样子我们在linux系统上,如果不想打印出这些log,可以向这个文件写其他标志位。

代码示例如下:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdarg.h>
void printf_my_log(char *file, int line, const char *func, char *fmt, ...)
{
int fd;
char filename[] = "printlog";
char readbuf[8] = {0};
int read_count = 0;
int logFlag = 0;
//打开文件printlog,如果文件不存在,则创建新文件
if((fd = open(filename,O_RDWR|O_CREAT, 0777))<0)
{
perror("open");
}
//读取文件一个字符到readbuf数组中
read_count = read(fd,readbuf,1);
//把字符串转化为数字。
logFlag = atoi(readbuf);
//没有向文件写入数据或者读取到的标志位为1,则打印log
if (read_count < 1 || logFlag == 1)
{
char argvStr[1024] = {0};
va_list argptr;
printf("[%s(Line=%d):%s]",file,line,func);
va_start (argptr, fmt);
vsprintf(argvStr, fmt, argptr);
va_end (argptr);
printf("%s\n", argvStr);
}
close(fd);
}

int main(void)
{
int a = 10;
printf_my_log(__FILE__,__LINE__,__FUNCTION__,"%d",a);
return 0;
}


在终端实验,

编译:

ubuntu:~/test/69test$ gcc creatfile.c -o creatfile
ubuntu:~/test/69test$ ls
creatfile  creatfile.c  printlog


运行应用程序:(默认打开log)

ubuntu:~/test/69test$ ./creatfile
[creatfile.c(Line=54):main]10


关闭log:

ubuntu:~/test/69test$ echo 0 > printlog
ubuntu:~/test/69test$ ./creatfile


打开log:

ubuntu:~/test/69test$ echo 1 > printlog
ubuntu:~/test/69test$ ./creatfile [creatfile.c(Line=54):main]10


上面这种做法,实践学习还可以,但是应用到项目中,效率太低,不建议使用。本节先介绍比较低效的方法,然后在接下来的博客中会继续思考如何改进这些问题。在分析存在的问题之后再思考如何改进这些问题的方法,有助于加深对改进机智的理解,同时也能更深刻的学习总结知识。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: