有多种方法可以做到这一点。您可以在 GitHub 上的 SOQ(堆栈溢出问题)存储库中找到执行此类操作的一些代码,即 src/libsoq 子目录中的文件 stderr.c 和 stderr.h。这是我多年来开发的一个包(最早的版本我仍然有记录,可以追溯到 1988 年),我在大多数 C 程序中都使用它。
现在使用的方案通过将要格式化的数据转换为字符串来确保只有一次写入操作——请参阅err_fmtmsg()——然后使用适当的写入机制(标准 I/O,例如fprintf(),或
write() 或 syslog()) 将消息发送到输出机制。
static size_t err_fmtmsg(char *buffer, size_t buflen, int flags, int errnum,
const char *format, va_list args)
{
char *curpos = buffer;
char *bufend = buffer + buflen;
buffer[0] = '\0'; /* Not strictly necessary */
if ((flags & ERR_NOARG0) == 0)
curpos = efmt_string(curpos, bufend, "%s: ", arg0);
if (flags & ERR_LOGTIME)
{
char timbuf[32];
curpos = efmt_string(curpos, bufend,
"%s - ", err_time(flags, timbuf, sizeof(timbuf)));
}
if (flags & ERR_PID)
curpos = efmt_string(curpos, bufend,
"pid=%d: ", (int)getpid());
curpos = vfmt_string(curpos, bufend, format, args);
if (flags & ERR_ERRNO)
curpos = efmt_string(curpos, bufend,
"error (%d) %s\n", errnum, strerror(errnum));
assert(curpos >= buffer);
return((size_t)(curpos - buffer));
}
如你所见,这可以为arg0产生的消息添加前缀(程序名称,通过函数err_setarg0()设置;它可以添加一个PID;它可以添加时间戳(带有整数选项)秒、毫秒、微秒、纳秒在flags的控制下),也可以附加错误号和相应的系统错误信息。
这是隐藏在系统内部的一个功能。在外部级别,入口点之一是extern void err_syserr(const char *fmt, ...);——这会自动添加系统错误,在标准错误上打印消息,然后退出程序。还有很多其他的日志入口点,其中一些退出,一些返回。还有很多控件。
注意err_fmtmsg() 函数接受一个参数va_list args。这通常是最好的工作方式。您应该使用此方案编写代码:
void simple_printf(const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
simple_vprintf(fmt, args);
va_end(args);
}
void simple_vprintf(const char* fmt, va_list args)
{
/* … preamble … */
vprintf(fmt, args);
/* … postamble … */
}
您使用va_list 函数编写主函数,并使用调用主函数的... 提供方便的接口,如上所示。
如果您打算多次调用标准 I/O 写入函数(fprintf() 等),请考虑使用flockfile()
和funlockfile() 保持输出“原子”。
void simple_vprintf(const char* fmt, va_list args)
{
flockfile(stdout);
/* … preamble — possibly writing to stdout … */
vprintf(fmt, args);
/* … postamble — possibly writing to stdout … */
funlockfile(stdout);
}
您还可以考虑提供以下功能:
extern void simple_fprintf(FILE *fp, const char *fmt, ...);
extern void simple_vfprintf(FILE *fp, const char *fmt, va_list args);
然后simple_vprintf() 将简单地调用simple_vfprintf(stdout, fmt, args)。然后,您可能会开始查看头文件中覆盖函数的 static inline 实现。
一段时间后,如果您实施了足够多的变体,您就会开始侵犯stderr.[ch] 中的实施。