#和##的使用以及思考c实现日志打印

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;
}
上一篇:C语言35——可变参数


下一篇:js获取中国省市区,省市筛选、省市、省市筛选联动。【C#】【js】