Last Updated: 2023-05-10 07:12:37 Wednesday
-- TOC --
本文介绍如何用C语言实现参数数量不定的函数接口,然后实现了一个同事写stdout和file的日志接口,最后给出这个接口的更简单的实现方式。
...
是实现不定数量参数的语法符号。
如申明:
void va_func(int a, int b, ...);
C标准库提供了一套接口,用来按顺序取出参数,使用需包括如下头文件:
#include <stdarg.h>
示例代码:
#include <stdio.h>
#include <stdarg.h>
void va_func(int num, ...){
int sum = 0;
va_list ap; // declare
va_start(ap, num); // init
for(int i=0; i<num; ++i)
sum += va_arg(ap, int); // fetch by type
va_end(ap); // end up
printf("%d\n", sum);
}
int main(){
va_func(5,1,2,3,4,5);
va_func(10,1,2,3,4,5,6,7,8,9,10);
va_func(0);
return 0;
}
...
前的那个参数(last参数),用这个参数是为了定位出不定数量参数开始的位置。*(type*)
去取值。(printf的last参数内,就提供后后续不定数量参数的类型信息)基于前文的基础,下面是用C语言写了一个log接口,模仿printf,参数个数不定,实现了可同时输出到stdout和file的效果,可灵活指定输出的文件名,并且线程安全,自动打印timestamp。
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <pthread.h>
#include <sys/time.h>
pthread_mutex_t _log_mutex;
void lg_init(void) {
pthread_mutex_init(&_log_mutex, NULL);
}
void lg_destroy(void) {
pthread_mutex_destroy(&_log_mutex);
}
// with millisecond info
void get_timestamp_str(char *buf) {
struct timeval now;
gettimeofday(&now, NULL);
struct tm *timenow;
timenow = localtime(&now.tv_sec);
strftime(buf, 64, "[%Y-%m-%d %H:%M:%S", timenow);
sprintf(buf+strlen(buf), ".%03ld] ", now.tv_usec/1000);
}
#define LOG_BUFFER_LEN 256
#define LOG_DEST_DEFAULT 0
#define LOG_DEST_FILE 1
#define LOG_DEST_STDOUT 2
#define LOG_DEST_NONE 3
#define _OUTPUT(buf, logf, opt)\
do{\
if(opt == LOG_DEST_DEFAULT){\
fprintf(logf, "%s", buf);\
fprintf(stdout, "%s", buf);\
}\
else if(opt == LOG_DEST_FILE)\
fprintf(logf, "%s", buf);\
else if(opt == LOG_DEST_STDOUT)\
fprintf(stdout, "%s", buf);\
}while(0);
// The name log is conflict with log in <math.h>,
// so here is lg.
void lg(char *pn,
const unsigned int opt,
char *fmt, ...) {
char buf[LOG_BUFFER_LEN] = {};
// check params
if(opt > 2)
return;
if((opt != 2) && (pn == NULL)){
fprintf(stderr, "[lg] pn should not be NULL.\n");
return;
}
// lock mutex
pthread_mutex_lock(&_log_mutex);
// open log file if necessary
FILE *logf = NULL;
if(opt != 2)
if((logf=fopen(pn, "a")) == NULL){
fprintf(stderr, "[lg] open log file %s error.\n", pn);
return;
}
// write timestamp
get_timestamp_str(buf);
_OUTPUT(buf, logf, opt);
// write variadic params
char *p;
va_list ap;
va_start(ap, fmt);
p = memchr(fmt, '%', strlen(fmt));
while(p){
if(p != fmt){
size_t len = p - fmt;
if(len > LOG_BUFFER_LEN){
fprintf(stderr, "[lg] exceed LOG_BUFFER_LEN.\n");
goto end;
}
memset(buf, 0, LOG_BUFFER_LEN);
memcpy(buf, fmt, p-fmt);
_OUTPUT(buf, logf, opt);
}
switch(*++p){
case 'c':{
// need a cast here since va_arg only
// takes fully promoted types
char c[2] = {};
c[0] = (char)va_arg(ap, int);
_OUTPUT(c, logf, opt);
break;
}
case 's':{
char *s = va_arg(ap, char*);
if(LOG_BUFFER_LEN < (strlen(s)+1)){
fprintf(stderr, "[lg] exceed LOG_BUFFER_LEN (s).\n");
goto end;
}
memset(buf, 0, LOG_BUFFER_LEN);
sprintf(buf, "%s", s);
_OUTPUT(buf, logf, opt);
break;
}
case 'd':{
char b[16] = {};
int a = va_arg(ap, int);
sprintf(b, "%d", a);
_OUTPUT(b, logf, opt);
break;
}
case 'u':{
char b[16] = {};
int a = va_arg(ap, unsigned int);
sprintf(b, "%u", a);
_OUTPUT(b, logf, opt);
break;
}
default:
fprintf(stderr, "[lg] unsupported format.\n");
goto end;
}
fmt = ++p;
p = memchr(fmt, '%', strlen(fmt));
}
_OUTPUT(fmt, logf, opt);
end:
// close resources
va_end(ap);
if (logf != NULL)
fclose(logf);
pthread_mutex_unlock(&_log_mutex);
}
int main(){
lg_init();
lg("test.log", 0, "");
lg("test.log", 0, "\n");
lg("test.log", 0, "abcdefg\n");
lg("test.log", 0, "%s\n", "123456789");
lg("test.log", 0, "%sabcdefg\n", "123456789");
lg("test.log", 0, "%d\n", 999);
lg("test.log", 0, "%d\n", -999);
lg("test.log", 0, "%u\n", 999);
lg("test.log", 0, "%u\n", -999);
lg("test.log", 0, "%c\n", '!');
lg("test.log", 0, "%c\n", '$');
lg("test.log", 0, "a%s2%d3%u4%ce\n", "a", 2, 3, '4');
//lg("test.log", 0, "%x\n", -999);
lg_destroy();
return 0;
}
我们完全可以使用vprintf
和vfprintf
接口,更简单也更完整的实现前文的日志接口:
#include <stdio.h>
#include <stdarg.h>
#include <time.h>
#include <string.h>
#include <sys/time.h>
#include <pthread.h>
pthread_mutex_t _slog_mutex;
void slog_init() {
pthread_mutex_init(&_slog_mutex, NULL);
}
void slog_destroy() {
pthread_mutex_destroy(&_slog_mutex);
}
// timestamp with millisecond
void make_timestamp(char *buf, int buf_len) {
if(buf_len < 32){
fprintf(stderr, "[make_timestamp] buf_len is too small.\n");
return;
}
struct timeval now;
gettimeofday(&now, NULL);
struct tm *timenow;
timenow = localtime(&now.tv_sec);
strftime(buf, 32, "[%Y-%m-%d %H:%M:%S", timenow);
sprintf(buf+strlen(buf), ".%03ld]", now.tv_usec/1000);
}
// thread-safe simple log
void slog(char *fname, char *fmt, ...){
pthread_mutex_lock(&_slog_mutex);
FILE *logf = NULL;
if(fname != NULL){
logf = fopen(fname, "a");
if(!logf){
fprintf(stderr, "[slog] open log file error.\n");
pthread_mutex_unlock(&_slog_mutex);
return;
}
}
va_list ap1,ap2;
va_start(ap1, fmt);
va_copy(ap2, ap1);
// timestamp
char tbuf[32] = {};
make_timestamp(tbuf, 32);
// write to stdout and file respectively
// by using different va_list variable
printf("%s ", tbuf);
vprintf(fmt, ap1);
if(logf){
fprintf(logf, "%s ", tbuf);
vfprintf(logf, fmt, ap2);
fclose(logf);
}
va_end(ap1);
va_end(ap2);
pthread_mutex_unlock(&_slog_mutex);
}
int main(){
slog_init();
slog(NULL, "");
slog("tlog.log", "abc %d %s 0x%x 123\n", 1, "2", 333);
slog(NULL, "999 %d %s 0x%x 123\n", 1, "2", 333);
slog_destroy();
return 0;
}
可以看出来,va_copy接口的作用是在复制地址。
本文链接:https://cs.pynote.net/sf/c/202207101/
-- EOF --
-- MORE --