沉淀之log4c的error
log4c的实现中在自身调试的时候也实现了一套调试接口,分别是sd_debug和sd_error。在error.h 和 error.c中进行了实现,以下是对于源码的分析:
error.h:
#ifndef __sd_error_h
#define __sd_error_h
#include <stdarg.h>
#include "defs.h"
extern int sd_debug(const char *fmt, ...) SD_ATTRIBUTE((format(printf, 1, 2)));
extern int sd_error(const char *fmt, ...) SD_ATTRIBUTE((format(printf, 1, 2)));
#endif /* __sd_error_h */
这个就是error.h文件中的内容,声明了两个函数:sd_debug和sd_error;使用了一个编译属性SD_ATTRIBUTE((format(printf, 1, 2)));所以error.h包含的def.h里面会有说明。
另外对于defs.h应该也要拿出来说一下:
defs.h:
#ifndef __sd_defs_h
#define __sd_defs_h
#ifdef __cplusplus
# define __SD_BEGIN_DECLS extern "C" {
# define __SD_END_DECLS }
#else
# define __SD_BEGIN_DECLS
# define __SD_END_DECLS
#endif
/* GNU C attribute feature,
* for the public API macro LOG4C_ATTRIBUTE is used instead */
#ifdef __GNUC__
#define SD_ATTRIBUTE(X) __attribute__(X)
#else
#define SD_ATTRIBUTE(X)
#endif
#endif
第四到第八行使用了条件编译,根据是否有__cplusplus这个宏来进行C++兼容性编译(C++里面定义了宏__cplusplus,C里面没有),这样在每个自定义的头文件的开始使用__SD_BEGIN_DECLS宏,然后在头文件的结束时使用__SD_END_DECLS,在error.h中就用到了这两个宏,这样在使用g++进行编译时就做到了兼容性。
至于为什么extern "C" {***}能够让C和C++兼容,就要提到了C++里面的函数重载,C++里面同一个函数名可以根据参数不同进行多次重载,而且每次重载的功能也是不一样的,在链接的时候会将各个重载函数连带上其参数信息生成唯一的表示符号,然后进行链接。但是在C里面,编译时函数名字的前面只加上了一个下划线。这样如果在C++里面调用C的函数,可能就会导致链接找不到对应的代码块而导致链接失败。例如:
test(int a, int b)
在gcc编译时所生成的符号是_test
在g++编译时所生成的符号是testii
所以链接器找不到c文件中所定义的test函数是很显然的,所有引入了extern "C" {***},这个就能让g++在编译时都按照同一个格式来进行编译符号然后进行愉快的链接。具体的更详细的说明可以在网上进一步的学习(附件里有一篇pdf来自网上,页脚有链接)。
第十四行到第十八行根据__GNUC__条件编译进行__attribute__编译属性的选择。为什么是用这个来进行条件选择?因为GNUC的一大特色就是__attribute__机制。可以设置函数属性,变量属性,类型属性。
在error.h中出现的SD_ATTRIBUTE((format(printf, 1, 2)));按照宏定义展开之后就是:__attribute__((format(printf, 1, 2)));这个属性的意思是:提示编译器,对这个函数的调用需要像printf一样,用对应的format字符串来check可变参数的数据类型。
例如:
extern int my_print(int lev, const char *format, ...)__attribute__((format(printf,2,3)));
这里__attribute__((format(printf,2,3)))告诉编译器,参数中的format相当于printf的format,可变参数是从第三个参数开始的。这样编译器就会在编译时用和printf一样的check法则来确认可变参数是否正确了。
以上就讲清楚了error.h和defs.h中的内容,所以接下来就是error.c文件了。
error.c
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#ifdef HAVE_STDARG_H
# include <stdarg.h>
#else
# ifdef HAVE_VARARGS_H
# include <varargs.h>
# endif
#endif
#include "error.h"
#include "sd_xplatform.h"
int sd_debug(const char *fmt, ...)
{
va_list args;
int r;
if (!getenv("SD_DEBUG"))
return 0;
r = fprintf(stderr, "[DEBUG] ");
va_start(args, fmt);
r += vfprintf(stderr, fmt, args);
va_end(args);
r += fprintf(stderr, "\n");
return r;
}
int sd_error(const char *fmt, ...)
{
va_list args;
int r;
if (!getenv("SD_ERROR"))
return 0;
r = fprintf(stderr, "[ERROR] ");
va_start(args, fmt);
r += vfprintf(stderr, fmt, args);
va_end(args);
r += fprintf(stderr, "\n");
return r;
}
最开始是条件编译进行控制头文件的包含,然后是包含自己的头文件,以及一个多平台架构的头文件(后续分析)。然后接下来就是两个函数的函数体定义了,其实就是一目了然的打印过程,就不啰嗦了。对于其中出现的,接下来要进行分析的内容会提一下:
va_list:可变参数,这个后续会进行原理性的分析。
getenv()是系统调用,根据参数中的字符串在环境变量里面进行匹配对应的值。
至于使用其实就是两个函数的调用,debug还要使用的时候还要设置一下环境变量才能够生效,设置方法export SD_DEBUG=1和export SD_ERROR=1;
main.c
#include "error.h"
int main()
{
sd_debug("hehehheh\n");
sd_error("hehehheh\n");
return 0;
}
输出结果如下:
[DEBUG] hehehheh
[ERROR] hehehheh
附件列表