实现参数数量不定的函数接口

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;
}

同时写stdout和file的log接口

基于前文的基础,下面是用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;
}

更简单更完整的实现

我们完全可以使用vprintfvfprintf接口,更简单也更完整的实现前文的日志接口:

#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 --