c语言日志模块的可实现方案:
内容摘自网络,梳理出来。。。
1:#和## 属于预处理标记: #后面转换为字符串 ##预处理拼接标记
2:VA_ARGS C99中新增的特性,支持宏定义中支持可变参数,接收…传递多个参数。
使用在使用了...的宏定义中: #define myprintf(...) fprintf(stderr, __VA_ARGS__)
3:如何解析不定参数? ==》应该是__VA_ARGS__ 底层实现,使用va_list
4:#VA_ARGS 仅仅展开列表对应的字符串了。
5:##VA_ARGS 是GUN的特性,c标准不建议这样用。
解决__VA_ARGS__参数个数为0时,宏定义最后的,会编译报错问题。
##在如果参数为0时,会把宏定义中前面的逗号删除掉。
6:c语言中printf输出其实就是上面的逻辑思路:
#include <stdio.h> int printf(const char *format, ...); //输出到标准输出 int fprintf(FILE *stream, const char *format, ...); //输出到文件 int sprintf(char *str, const char *format, ...); //输出到字符串str中 int snprintf(char *str, size_t size, const char *format, ...); //按size大小输出到字符串str中 下面系列把一个个变量用va_list调用所替代: int vprintf(const char *format, va_list ap); int vfprintf(FILE *stream, const char *format, va_list ap); int vsprintf(char *str, const char *format, va_list ap); int vsnprintf(char *str, size_t size, const char *format, va_list ap);
7:redis中分析:
#include <stdio.h> #define D(...) \ do { \ FILE *fp = fopen("/tmp/log.txt","a"); \ fprintf(fp,"%s:%s:%d:\t", __FILE__, __func__, __LINE__); \ fprintf(fp,__VA_ARGS__); \ fprintf(fp,"\n"); \ fclose(fp); \ } while (0); #define debugf(...) \ if (raxDebugMsg) { \ printf("%s:%s:%d:\t", __FILE__, __FUNCTION__, __LINE__); \ printf(__VA_ARGS__); \ fflush(stdout); \ } //epos是一个全局变量,每一次在调用该宏时,获取文件读写指针当前位置 ftello static char error[1044]; static off_t epos; #define ERROR(...) { \ char __buf[1024]; \ snprintf(__buf, sizeof(__buf), __VA_ARGS__); \ snprintf(error, sizeof(error), "0x%16llx: %s", (long long)epos, __buf); \ }
8: 知识点样例代码
//对描述提到的问题进行测试 #include <stdio.h> #include <stdarg.h> //1:#的测试,转换为字符 可以用这种方式转换打印字符串 #define P(A) printf("%s:%d\n",#A,A) #define SQUARE(x) printf("The square of "#x" is %d.\n", ((x)*(x))) //其实也是个字符串拼接 #define MY_TOSTR(var) (#var"[test1]\n") void test_1() { P(88); //把int 8转为字符直接输出 //使用场景 int a=11, b=12; P(a+b); //求8的平方 SQUARE(8); printf("%s", MY_TOSTR(8888)); } //2: ##的测试,可以实现字符的拼接,使用场景:自动生成代码,可以用来拼接函数名 // static const char* FUNC = "_func"; //这玩意是直接拼接参数的东东 #define my_math(x, y) (x##e##y) //linux下直接用空格可以实现拼接 #define XNAME(name, type) #name #type //linux下的拼接直接用一个空格 #define SOFTWARE_VERSION "Software:V1.00" #define HARDWARE_VERSION "Hardware:V1.00" #define SYSTEM_VERSION SOFTWARE_VERSION HARDWARE_VERSION //TODO: linux下如何实现函数名的拼接?自动生成一些代码 可以参考thritf? 代码生成器相关 void test_2() { printf("XNAME:%s \n", XNAME(44, 55)); printf("version: [%s] \n", SYSTEM_VERSION); printf("%e \n", my_math(3, 4)); //函数名拼接 return; } //3: C99新增__VA_ARGS__,改变了可变参数不能用在宏中的定义。 #define debug(...) printf(__VA_ARGS__) void test_3() { debug("test of __VA_ARGS__ [%d]:[%s] \n", 0, "test"); debug("test of __VA_ARGS__ error \n"); } //但是如果这样定义,会出错,因为当没有参数时,宏定义fmt后面的逗号是多余的 //fmt把输出的格式串提取出来了,上面是直接放在了... #define debug1(fmt, ...) printf(fmt, __VA_ARGS__) void test_3_1() { debug1("test of __VA_ARGS__ [%d]:[%s] \n", 0, "test"); // expected expression before ‘)’ token // debug1("test of __VA_ARGS__ error \n"); //这个应该会报错,因为多了一个逗号 } // 4: 用##来优化test_3中把fmt提取出来,如果没有参数就会报错 // 其他思路: 实际的输出用printf系列的其他函数 也可以写入文件 #define debug2(fmt, ...) printf(fmt, ##__VA_ARGS__) #define debug3(fmt, ...) fprintf (stderr, fmt, ##__VA_ARGS__) void test_4() { debug2("test of ##__VA_ARGS__ [%d]:[%s] \n", 0, "test"); debug2("test of ##__VA_ARGS__ success \n"); debug3("test of ##__VA_ARGS__ [%d]:[%s] \n", 0, "test"); debug3("test of ##__VA_ARGS__ success \n"); } // 5: va_list的测试 第一个参数是参数要相加的参数的个数 int sum(int num_args, ...) { int val = 0; va_list ap; int i; va_start(ap, num_args); for(i = 0; i < num_args; i++) { val += va_arg(ap, int); } va_end(ap); return val; } void stdio_printf(const char* fmt, ...) { int len, i; va_list ap; char buffer[256]; va_start(ap, fmt); len = vsnprintf(buffer, sizeof(buffer), (const char*)fmt, ap); va_end(ap); printf("buffer: %s \n",buffer); } void test_5() { printf("15 和 56 的和 = %d\n", sum(2, 15, 56) ); printf("add :%d \n", sum(3,2,3,4)); stdio_printf("%s %d \n", "test_5", 5); } int main() { //#的测试 test_1(); test_2(); //##的测试 TODO linux下##来自动生成代码拼接函数名的实现 test_3(); test_3_1(); test_4(); test_5(); return 0; }
相关样例代码:
#include <stdio.h> #define LOG(level, format, ...) \ fprintf(stderr, "[%s|%s@%s:%d] " format "\n", \ level, __func__, __FILE__, __LINE__, ##__VA_ARGS__ )
可写入文件:
#include <stdio.h> #include <stdarg.h> #include <time.h> void logC(const char *func, const char *file, const int line, const char *type, const char *format, ...) { FILE *file_fp; time_t loacl_time; char time_str[128]; // 获取本地时间 time(&loacl_time); strftime(time_str, sizeof(time_str), "[%Y.%m.%d %X]", localtime(&loacl_time)); // 日志内容格式转换 va_list ap; va_start(ap, format); char fmt_str[2048]; vsnprintf(fmt_str, sizeof(fmt_str), format, ap); va_end(ap); // 打开日志文件 file_fp = fopen("./main.log", "a"); // 写入到日志文件中 if (file_fp != NULL) { fprintf(file_fp, "[%s]%s[%s@%s:%d] %s\n", type, time_str, func, file, line, fmt_str); fclose(file_fp); } else { fprintf(stderr, "[%s]%s[%s@%s:%d] %s\n", type, time_str, func, file, line, fmt_str); } } #define LOGC(type, format, ...) logC(__func__, __FILE__, __LINE__, type, format, ##__VA_ARGS__) int main() { LOGC("LOG_DEBUG", "a=%d", 10); return 0; }