C 变量函数:提取参数并追加字符串

C 变量函数:提取参数并追加字符串,c,variadic-functions,C,Variadic Functions,我有一个函数log\u info(复制自printf的实现),它接受变量数量的参数并将其传递给vprintf: int log_info(const char *format, ...) { va_list arg; int done; va_start (arg, format); done = vfprintf (stdout, format, arg); va_end (arg); return done; } 我想在这些参数前加上字符串。因此,如果用户以这种

我有一个函数
log\u info
(复制自
printf
的实现),它接受变量数量的参数并将其传递给vprintf:

int log_info(const char *format, ...) {
  va_list arg;
  int done;

  va_start (arg, format);
  done = vfprintf (stdout, format, arg);
  va_end (arg);

  return done;
}
我想在这些参数前加上字符串。因此,如果用户以这种方式调用上述函数:

log_info("at iteration %d, float value is %f", i, f);
而不是印刷

at iteration 4, float value is 102.34
我想打印

[INFO] [at iteration 4, float value is 102.34] [timestamp: xxxx]
我可以分三步来做

fprintf(stdout, "[INFO] [");
vprintf(stdout, format, arg);
fprintf(stdout, "] [timestamp:%f]", ts);
但该程序是多线程的,因此我希望所有数据都在对vprintf的一次调用中写入(vprintf是线程安全的)

另一个选项是锁定一个互斥锁,然后按照上面所示的3个步骤编写它,但是如果将字符串附加到args不太复杂,我想尝试一下

编辑:由于使用互斥锁而导致的性能开销不是一个真正的问题,但除非必要,否则我不想使用互斥锁


提前感谢

您希望向输出中添加新字段,因此只需构造一个新的格式字符串即可

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

int loginfo(const char *format, ...) {
    int ret;
    int len;
    va_list ap;
    char *new_fmt;
    char *timestamp;
    const char fmt_template[] = "[INFO] [%s] [timestamp: %s]";

    /* Grab the timestamp now, since if we call it twice, it may change in length */
    timestamp = get_time_string();

    /* Calculate length for the augmented format string and allocate. */
    len = snprintf(NULL, 0, fmt_template, format, timestamp);
    new_fmt = malloc(len + 1);

    /* Construct the new format string */
    snprintf(new_fmt, len + 1, fmt_template, format, timestamp);

    /* Print as before, using new format string */
    va_start (ap, format);
    ret = vfprintf (stdout, new_fmt, ap);
    va_end (ap);

    free(new_fmt);
    return ret;
}
#包括
#包括
#包括
int loginfo(常量字符*格式,…){
int ret;
内伦;
va_列表ap;
char*新的_fmt;
字符*时间戳;
const char fmt_模板[]=“[INFO][%s][时间戳:%s]”;
/*现在抓取时间戳,因为如果我们调用它两次,它的长度可能会改变*/
时间戳=获取时间字符串();
/*计算扩展格式字符串的长度并分配*/
len=snprintf(NULL,0,fmt_模板,格式,时间戳);
new_fmt=malloc(len+1);
/*构造新的格式字符串*/
snprintf(新的fmt,len+1,fmt模板,格式,时间戳);
/*使用新格式字符串像以前一样打印*/
va_开始(ap,格式);
ret=vfprintf(标准输出、新输出、ap);
va_端(ap);
免费(新fmt);
返回ret;
}

您希望向输出中添加新字段,因此只需构造一个新的格式字符串即可

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

int loginfo(const char *format, ...) {
    int ret;
    int len;
    va_list ap;
    char *new_fmt;
    char *timestamp;
    const char fmt_template[] = "[INFO] [%s] [timestamp: %s]";

    /* Grab the timestamp now, since if we call it twice, it may change in length */
    timestamp = get_time_string();

    /* Calculate length for the augmented format string and allocate. */
    len = snprintf(NULL, 0, fmt_template, format, timestamp);
    new_fmt = malloc(len + 1);

    /* Construct the new format string */
    snprintf(new_fmt, len + 1, fmt_template, format, timestamp);

    /* Print as before, using new format string */
    va_start (ap, format);
    ret = vfprintf (stdout, new_fmt, ap);
    va_end (ap);

    free(new_fmt);
    return ret;
}
#包括
#包括
#包括
int loginfo(常量字符*格式,…){
int ret;
内伦;
va_列表ap;
char*新的_fmt;
字符*时间戳;
const char fmt_模板[]=“[INFO][%s][时间戳:%s]”;
/*现在抓取时间戳,因为如果我们调用它两次,它的长度可能会改变*/
时间戳=获取时间字符串();
/*计算扩展格式字符串的长度并分配*/
len=snprintf(NULL,0,fmt_模板,格式,时间戳);
new_fmt=malloc(len+1);
/*构造新的格式字符串*/
snprintf(新的fmt,len+1,fmt模板,格式,时间戳);
/*使用新格式字符串像以前一样打印*/
va_开始(ap,格式);
ret=vfprintf(标准输出、新输出、ap);
va_端(ap);
免费(新fmt);
返回ret;
}

一种方法是使用vsnprintf将原始消息写入(动态分配的)字符串。然后使用fprintf输出元数据和原始消息:

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

static const char * get_timestamp_str()
{
    ...
}

static void Log(const char *format, ...)
{
    va_list arg;
    int len;
    char * orig_msg;

    /* Compute length of original message */
    va_start(arg, format);
    len = vsnprintf(NULL, 0, format, arg);
    va_end(arg);

    /* Allocate space for original message */
    orig_msg = (char *)calloc(len+1, sizeof(char));

    /* Write original message to string */
    va_start(arg, format);
    vsnprintf(orig_msg, len+1, format, arg);
    va_end(arg);

    /* Write metadata plus original message to stderr */
    fprintf(stderr, "[INFO] [timestamp %s] %s\n", get_timestamp_str(), orig_msg);
    free(orig_msg);
}
#包括
#包括
#包括
...
静态常量char*get_timestamp_str()
{
...
}
静态无效日志(常量字符*格式,…)
{
va_列表参数;
内伦;
原味味精;
/*计算原始消息的长度*/
va_开始(参数,格式);
len=vsnprintf(NULL,0,格式,arg);
va_端(arg);
/*为原始邮件分配空间*/
orig_msg=(char*)calloc(len+1,sizeof(char));
/*将原始消息写入字符串*/
va_开始(参数,格式);
vsnprintf(原始消息,len+1,格式,arg);
va_端(arg);
/*将元数据和原始消息写入stderr*/
fprintf(stderr,“[INFO][timestamp%s]%s\n”,get_timestamp_str(),orig_msg);
免费(原味味精);
}

一种方法是使用vsnprintf将原始消息写入(动态分配的)字符串。然后使用fprintf输出元数据和原始消息:

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

static const char * get_timestamp_str()
{
    ...
}

static void Log(const char *format, ...)
{
    va_list arg;
    int len;
    char * orig_msg;

    /* Compute length of original message */
    va_start(arg, format);
    len = vsnprintf(NULL, 0, format, arg);
    va_end(arg);

    /* Allocate space for original message */
    orig_msg = (char *)calloc(len+1, sizeof(char));

    /* Write original message to string */
    va_start(arg, format);
    vsnprintf(orig_msg, len+1, format, arg);
    va_end(arg);

    /* Write metadata plus original message to stderr */
    fprintf(stderr, "[INFO] [timestamp %s] %s\n", get_timestamp_str(), orig_msg);
    free(orig_msg);
}
#包括
#包括
#包括
...
静态常量char*get_timestamp_str()
{
...
}
静态无效日志(常量字符*格式,…)
{
va_列表参数;
内伦;
原味味精;
/*计算原始消息的长度*/
va_开始(参数,格式);
len=vsnprintf(NULL,0,格式,arg);
va_端(arg);
/*为原始邮件分配空间*/
orig_msg=(char*)calloc(len+1,sizeof(char));
/*将原始消息写入字符串*/
va_开始(参数,格式);
vsnprintf(原始消息,len+1,格式,arg);
va_端(arg);
/*将元数据和原始消息写入stderr*/
fprintf(stderr,“[INFO][timestamp%s]%s\n”,get_timestamp_str(),orig_msg);
免费(原味味精);
}

您不能构造新格式字符串的原因是什么?使用
sprintf
strcat
?按照惯例,时间戳跟随日志级别(“此处为INFO”),然后跟随日志消息。
%f”
看起来像是一个有趣的时间戳值,顺便说一句……您为什么不能构造新的格式字符串?使用
sprintf
strcat
?按照惯例,时间戳跟随日志级别(“此处为INFO”),之后是日志消息。
%f
看起来像是一个有趣的时间戳值,顺便说一句……Devlus的好捕获,我现在添加了
免费
vsnprintf
将消耗
arg
,因此,在调用之前,应使用
va_copy
将其复制到第二个va_列表中。@jared_schmitz:第二个va_启动将重新初始化arg,对吗?@GregPrisament:不。在大多数主要系统上可能会。请看这个问题:@jared_schmitz:我不同意。“va_end”的手册页指出:“va_start()的每次调用都必须与同一函数中的va_end()的相应调用相匹配。调用va_end(ap)后,变量ap未定义。可以多次遍历列表,每次遍历都用va_start()和va_end()括起来。”Good catch@Devolus,我现在已经添加了
免费的
vsnprintf
将消耗
arg
,因此应该在调用之前使用
va_copy
将其复制到第二个va_列表中。@jared_schmitz:第二次va_启动将重新初始化arg,对吗?@GregPrisament:不。在大多数主要系统上可能会这样。看到这个问题了吗