LV01-12-C语言-printf扩展

本文主要是C语言基础——printf扩展相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
Windows windows11
Ubuntu Ubuntu16.04的64位版本
VMware® Workstation 16 Pro 16.2.3 build-19376536
SecureCRT Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日
开发板 正点原子 i.MX6ULL Linux阿尔法开发板
uboot NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03)
linux内核 linux-4.15(NXP官方提供)
STM32开发板 正点原子战舰V3(STM32F103ZET6)
点击查看本文参考资料
参考方向 参考原文
------
点击查看相关文件下载
--- ---

前边我们已经了解到了 printf 函数是可以打印出带颜色的提示信息的,接下来我们来使用宏定义重新定义一下printf,便于我们调试。

一、颜色值

字体颜色和背景颜色值如下:

字体颜色 背景颜色
30 黑色 40 黑色
31 红色 41 红色
32 绿色 42 绿色
33 黄色 43 黄色
34 蓝色 44 蓝色
35 紫色 45 紫色
36 深绿色 46 深绿色
37 白色 47 白色

二、宏定义

1.调试类型

我们平时打印的信息大概会有这几种:错误、警告和信息

1
2
3
#define ERROR   "ERROR"           /* 错误 */
#define WARN "WARN " /* 警告 */
#define INFO "INFO " /* 信息 */

2.颜色定义

2.1字体

1
2
3
4
5
6
7
8
9
10
/* 颜色定义——字体颜色 */
#define CLS_ALL "\033[0m" /* 清除所有颜色 */
#define F_BLACK "\033[30m" /* 黑色字体 */
#define F_RED "\033[31m" /* 红色字体 */
#define F_GREEN "\033[32m" /* 绿色字体 */
#define F_YELLOW "\033[33m" /* 黄色字体 */
#define F_BLUE "\033[34m" /* 蓝色字体 */
#define F_PURPLE "\033[35m" /* 紫色字体 */
#define F_CYAN "\033[36m" /* 青色字体 */
#define F_WHITE "\033[37m" /* 白色字体 */

2.2背景

1
2
3
4
5
6
7
8
9
/* 颜色定义——背景颜色 */
#define B_BLACK "\033[40m" /* 黑色背景 */
#define B_RED "\033[41m" /* 红色背景 */
#define B_GREEN "\033[42m" /* 绿色背景 */
#define B_YELLOW "\033[43m" /* 黄色背景 */
#define B_BLUE "\033[44m" /* 蓝色背景 */
#define B_PURPLE "\033[45m" /* 紫色背景 */
#define B_CYAN "\033[46m" /* 青色背景 */
#define B_WHITE "\033[47m" /* 白色背景 */

2.3带背景的字体

1
2
3
4
/* 颜色定义——背景的字体 */
#define BLACK_RED "\033[30;41m" /* 红底黑字 */
#define BLACK_GREEN "\033[30;42m" /* 绿底黑字 */
#define BLACK_YELLOW "\033[30;43m" /* 黄底黑字 */

3.调试信息的开关

1
2
3
4
5
6
7
8
9
10
11
12
13
#define DEBUG_ENABLE  1           /* 是否开启调试信息 */
#define LOG_LEVEL DEBUG_ALL /* 调试信息显示级别 */


/* 调试等级枚举类型定义 */
enum DEBUG_LEVEL
{
DEBUG_OFF = 0,
DEBUG_ERROR = 1,
DEBUG_WARN = 2,
DEBUG_INFO = 3,
DEBUG_ALL = 4
};

4.宏定义

1
2
3
4
5
6
7
8
9
10
#define MYDEBUG(TYPE, COLOR, FMT, ARGS...) \
do \
{ \
if(DEBUG_ENABLE && (DEBUG_##TYPE <= LOG_LEVEL)) \
{ \
printf(COLOR); \
printf("[%s][%s:%s:%d]"FMT, TYPE, __FILE__, __FUNCTION__, __LINE__, ##ARGS); \
printf(CLS_ALL"\n"); \
} \
} while(0)

【参数说明】

  • TYPE :调试类型,可选值有ERROR、WARN和INFO。
  • COLOR : 打印信息的颜色。
  • FMT :格式化字符串,与 printf 一致。
  • ARGS … :可变参数,与 printf 一致。

三、时间戳

有的时候,我们想要打印出的数据带上时间戳,类似这样:

1
[12-4 12:00:05]

这样其实也是可以做到的,这样打印出时间戳有时候对我们也是很有用的。

1.时间函数

在linux中有很多的时间函数可以获取到当前的系统时间,由于我们的代码大部分是跑在linux系统上,所以这里就使用linux内核为我们提供的时间函数来获取时间戳。

1.1 clock_gettime()

在linux下可以使用man命令查看该函数的帮助手册。

1
2
3
4
5
/* 需包含的头文件 */
#include <time.h>

/* 函数声明 */
int clock_gettime(clockid_t clk_id, struct timespec *tp);

【函数说明】该函数用于获取时间,它的计量单位为十亿分之一,就是纳秒( ns )。

【函数参数】

  • clk_id : clockid_t 类型,表示要获取的时间的类型。
点击查看取值详情
1
2
3
4
5
6
7
CLOCK_REALTIME	         从1970.1.1到目前的时间
CLOCK_MONOTONIC 系统启动到现在的时间
CLOCK_THREAD_CPUTIME_ID 线程所消耗的时间
CLOCK_PROCESS_CPUTIME_ID 进程所消耗的时间
CLOCK_MONOTONIC_RAW 基于硬件时间
CLOCK_REALTIME_COARSE 不精确的REALTIME
CLOCK_MONOTONIC_COARSE 不精确的启动时间

在这里我们可以使用 CLOCK_REALTIME 来获取时间。

  • tp :truct timespec 类型结构体指针变量,他有两个成员,分别代表秒和纳秒。
点击查看结构体成员

结构体定义在 time.h 文件中

1
2
3
4
struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};

【返回值】 int 类型,成功返回 0,失败返回 -1。

【使用格式】一般情况下基本使用格式如下:

1
2
3
4
5
6
/* 需要包含的头文件 */
#include <time.h>

/* 至少应该有的语句 */
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);

【注意】 none

1.2 localtime() 函数

在 linux 下可以使用 man 3 localtime 命令查看该函数的帮助手册。

1
2
3
4
5
6
/* 需包含的头文件 */
#include <time.h>

/* 函数声明 */
struct tm *localtime(const time_t *timep);
struct tm *localtime_r(const time_t *timep, struct tm *result);

【函数说明】该函数可以把 time() 或 gettimeofday() 或者clock_gettime()得到的秒数( time_t 时间或日历时间)变成一个 struct tm 结构体所表示的时间,该时间对应的是本地时间。 localtime_r() 为 localtime() 函数的可重入版本,更加推荐使用。

【函数参数】

  • timep : time_t 类型指针变量,该参数包含了一个日历时间,也就是上边获取时间函数获取的那个秒数。

  • result : struct tm 类型结构体指针变量,该参数为可重入版本的函数 localtime_r() 所有,转换后的本地时间将存放在该参数指向的结构体中。

点击查看 struct tm 结构体成员

在使用 man 手册的时候,下边会有这个结构体成员的说明:

1
2
3
4
5
6
7
8
9
10
11
12
struct tm
{
int tm_sec; /* 秒 Seconds (0-60) */
int tm_min; /* 分 Minutes (0-59)*/
int tm_hour; /* 时 Hours (0-23) */
int tm_mday; /* 日 Day of the month (1-31) */
int tm_mon; /* 月 Month (0-11) */
int tm_year; /* 年 Year - 1900 */
int tm_wday; /* 周 Day of the week (0-6, Sunday = 0) */
int tm_yday; /* 一年中的第几天 Day in the year (0-365, 1 Jan = 0) */
int tm_isdst; /* 夏令时 Daylight saving time */
};

【注意】要想得到实际的时间,那么年份需要加上 1900 ,月份需要加上 1 。这是因为这个函数内部转换的时候,年份从 1900 开始算,月份从 0 开始算了。

【返回值】 struct tm 类型的结构体指针,成功则返回一个有效的 struct tm 结构体指针,而对于可重入版本 localtime_r() 来说,成功执行情况下,返回值将会等于参数 result ;失败则返回 NULL 。

【使用格式】一般情况下基本使用格式如下:

1
2
3
4
5
6
7
8
/* 需要包含的头文件 */
#include <time.h>

/* 至少应该有的语句 */
struct tm nowTime;
time_t tm;
tm = time(NULL);
localtime_r(&tm, &nowTime);

【注意】 none

2. 获取时间字符串

我们通过上边的两个函数就可以获取到一个时间,然后我们将其封装成一个函数,让其返回一个时间字符串,让printf打印的时候打印出来就可以啦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <time.h>
// 获取时间函数
char *timeString(void)
{
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
struct tm *timeinfo = localtime(&ts.tv_sec);
static char timeString[30];
sprintf(timeString, "%.2d-%.2d %.2d:%.2d:%.2d",
timeinfo->tm_mon + 1,
timeinfo->tm_mday,
timeinfo->tm_hour,
timeinfo->tm_min,
timeinfo->tm_sec);
return timeString;
}

通过这个函数我们就可以得到当前打印的时候的系统时间,例如:

1
printf("%s\n", timeString());

这样我们就会得到这样的一个时间:

1
12-04 08:23:15

我们用于printf的时候就会打印出这个时间字符串啦。

四、文件名

其实我们这个例子中只有一个文件,所以C语言的预定义宏 FILE 是可以只打印出文件名,其实这个是会包含路径的,当我们的工程目录较深的时候,又想知道是哪个文件中的打印数据,这而路径太长又会影响,我们只想要文件名怎么办呢?其实也是可以做到的,我们可以使用相关函数去掉路径。

1. strrchr()

在linux下可以使用man命令查看该函数的帮助手册。

1
2
3
4
5
/* 需包含的头文件 */
#include <string.h>

/* 函数声明 */
char *strrchr(const char *s, int c);

【函数说明】该函数用于在参数 s 所指向的字符串中搜索最后一次出现字符 c(一个无符号字符)的位置,并返回这个位置,注意这里的返回值也是指针类型,这也就意味着它返回的是一个地址,我们可以使用这个地址访问这个字符 c 位置开始的数据。这样我们就可以使用这个函数来找到路径中的 / 字符,然后只要它后边的文件名即可。

【函数参数】

  • s : char *类型,表示要查找的字符串。

  • c : int 类型,表示要查找的字符。

【返回值】 char *类型,该函数返回 str 中最后一次出现字符 c 的位置。如果未找到,则函数返回一个空指针。

【使用格式】none

【注意】 none

2. 获取文件名

我们就可以定义一个宏,来获取文件名,如下所示:

1
2
3
4
// windows
#define filename(x) (strrchr(x,'\\')?(strrchr(x,'\\') + 1):x)
// linux
#define filename(x) (strrchr(x,'/')?(strrchr(x,'/')+1):x)

这样,我们就可以这样使用定义的宏啦:

1
printf("%s\n", filename("/a/b/c/d/file.c"));

然后我们就会得到一个不包含路径的文件名啦、

五、使用实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <stdio.h>

#define DEBUG_ENABLE 1 /* 是否开启调试信息 */
#define LOG_LEVEL DEBUG_ALL /* 调试信息显示级别 */

#define ERROR "ERROR" /* 错误 */
#define WARN "WARN " /* 警告 */
#define INFO "INFO " /* 信息 */
/* 颜色定义——字体颜色 */
#define CLS_ALL "\033[0m" /* 清除所有颜色 */
#define F_BLACK "\033[30m" /* 黑色字体 */
#define F_RED "\033[31m" /* 红色字体 */
#define F_GREEN "\033[32m" /* 绿色字体 */
#define F_YELLOW "\033[33m" /* 黄色字体 */
#define F_BLUE "\033[34m" /* 蓝色字体 */
#define F_PURPLE "\033[35m" /* 紫色字体 */
#define F_CYAN "\033[36m" /* 青色字体 */
#define F_WHITE "\033[37m" /* 白色字体 */
/* 颜色定义——背景颜色 */
#define B_BLACK "\033[40m" /* 黑色背景 */
#define B_RED "\033[41m" /* 红色背景 */
#define B_GREEN "\033[42m" /* 绿色背景 */
#define B_YELLOW "\033[43m" /* 黄色背景 */
#define B_BLUE "\033[44m" /* 蓝色背景 */
#define B_PURPLE "\033[45m" /* 紫色背景 */
#define B_CYAN "\033[46m" /* 青色背景 */
#define B_WHITE "\033[47m" /* 白色背景 */
/* 颜色定义——背景的字体 */
#define BLACK_RED "\033[30;41m" /* 红底黑字 */
#define BLACK_GREEN "\033[30;42m" /* 绿底黑字 */
#define BLACK_YELLOW "\033[30;43m" /* 黄底黑字 */

/* 调试等级枚举类型定义 */
enum DEBUG_LEVEL
{
DEBUG_OFF = 0,
DEBUG_ERROR = 1,
DEBUG_WARN = 2,
DEBUG_INFO = 3,
DEBUG_ALL = 4
};

#define MYDEBUG(TYPE, COLOR, FMT, ARGS...) \
do \
{ \
if(DEBUG_ENABLE && (DEBUG_##TYPE <= LOG_LEVEL)) \
{ \
printf(COLOR); \
printf("[%s][%s:%s:%d]"FMT, TYPE, __FILE__, __FUNCTION__, __LINE__, ##ARGS); \
printf(CLS_ALL"\n"); \
} \
} while(0)


int main(int argc, char *argv[])
{
/* code */
MYDEBUG(ERROR, BLACK_RED, "%s", "hello World!");
MYDEBUG(INFO, BLACK_GREEN, "%s", "hello World!");
MYDEBUG(WARN, BLACK_YELLOW, "%s", "hello World!");

MYDEBUG(INFO, F_BLACK, "%s", "hello World!");
MYDEBUG(INFO, F_BLUE, "%s", "hello World!");
MYDEBUG(INFO, F_CYAN, "%s", "hello World!");
MYDEBUG(INFO, F_GREEN, "%s", "hello World!");
MYDEBUG(INFO, F_PURPLE, "%s", "hello World!");
MYDEBUG(INFO, F_RED, "%s", "hello World!");
MYDEBUG(INFO, F_WHITE, "%s", "hello World!");
MYDEBUG(INFO, F_YELLOW, "%s", "hello World!");

return 0;
}

显示效果如下:

image-20221108223600467

六、自用printf

经过上边的实例以及相关内容的了解,我们可以定义一个自己的printf函数,它可以包含时间戳,文件名,函数名以及函数的行号,这样在我们查看打印信息的时候会很方便。

1. 常用调试宏定义

1.1 debug_printf.h

点击查看详情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#ifndef __DEBUG_PRINTF_H__
#define __DEBUG_PRINTF_H__

#include <stdio.h>

#define DEBUG_ENABLE 1 /* 是否开启调试信息 */
#define LOG_LEVEL DEBUG_ALL /* 调试信息显示级别 */

#define ERROR "ERROR" /* 错误 */
#define WARN "WARN " /* 警告 */
#define INFO "INFO " /* 信息 */
/* 颜色定义——字体颜色 */
#define CLS_ALL "\033[0m" /* 清除所有颜色 */
#define F_BLACK "\033[30m" /* 黑色字体 */
#define F_RED "\033[31m" /* 红色字体 */
#define F_GREEN "\033[32m" /* 绿色字体 */
#define F_YELLOW "\033[33m" /* 黄色字体 */
#define F_BLUE "\033[34m" /* 蓝色字体 */
#define F_PURPLE "\033[35m" /* 紫色字体 */
#define F_CYAN "\033[36m" /* 青色字体 */
#define F_WHITE "\033[37m" /* 白色字体 */
/* 颜色定义——背景颜色 */
#define B_BLACK "\033[40m" /* 黑色背景 */
#define B_RED "\033[41m" /* 红色背景 */
#define B_GREEN "\033[42m" /* 绿色背景 */
#define B_YELLOW "\033[43m" /* 黄色背景 */
#define B_BLUE "\033[44m" /* 蓝色背景 */
#define B_PURPLE "\033[45m" /* 紫色背景 */
#define B_CYAN "\033[46m" /* 青色背景 */
#define B_WHITE "\033[47m" /* 白色背景 */
/* 颜色定义——背景的字体 */
#define BLACK_RED "\033[30;41m" /* 红底黑字 */
#define BLACK_GREEN "\033[30;42m" /* 绿底黑字 */
#define BLACK_YELLOW "\033[30;43m" /* 黄底黑字 */

/* 调试等级枚举类型定义 */
enum DEBUG_LEVEL
{
DEBUG_OFF = 0,
DEBUG_ERROR = 1,
DEBUG_WARN = 2,
DEBUG_INFO = 3,
DEBUG_ALL = 4
};
//===========================================================================
/* 自定义类型和字体颜色 */
#define MYDEBUG(TYPE, COLOR, FMT, ARGS...) \
do \
{ \
if(DEBUG_ENABLE && (DEBUG_##TYPE <= LOG_LEVEL)) \
{ \
printf(COLOR); \
printf("[%s][%s:%s:%d]"FMT, TYPE, __FILE__, __FUNCTION__, __LINE__, ##ARGS); \
printf(CLS_ALL"\n"); \
} \
} while(0)
//===========================================================================
/* 平时调试使用 */
#include <string.h>
#include <time.h> // 时间函数用
#define filename(x) (strrchr(x,'/')?(strrchr(x,'/')+1):x)
#define NORMAL_DEBUG 1 // 注意使用下边两个宏需要自己添加换行,比较符合平时的习惯
// 获取时间函数
char *timeString(void)
{
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
struct tm *timeinfo = localtime(&ts.tv_sec);
static char timeString[30];
sprintf(timeString, "%.2d-%.2d %.2d:%.2d:%.2d",
timeinfo->tm_mon + 1,
timeinfo->tm_mday,
timeinfo->tm_hour,
timeinfo->tm_min,
timeinfo->tm_sec);
return timeString;
}
// 获取系统时间戳 单位 ms
int sys_pts_get_ms(void)
{
struct timespec tv;
long long lasttime = 0;

clock_gettime(CLOCK_MONOTONIC, &tv);
lasttime = tv.tv_sec * 1000 + tv.tv_nsec / (1000 * 1000);
return lasttime;

}

#define HKPRT(fmt...) \
do \
{ \
if(NORMAL_DEBUG) \
{ \
printf(F_YELLOW); \
printf("[%s][HK_LOG][%s:%d][%s]", timeString(), filename(__FILE__), __LINE__, __FUNCTION__); \
printf(CLS_ALL); \
printf(fmt); \
} \
} while(0)
#define HKPRTE(fmt...) \
do \
{ \
if(NORMAL_DEBUG) \
{ \
printf(F_RED); \
printf("[%s][HK_LOG][%s:%d][%s]", timeString(), filename(__FILE__), __LINE__, __FUNCTION__); \
printf(CLS_ALL); \
printf(fmt); \
} \
} while(0)

#endif

1.2 main.c

点击查看详情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include "debug_printf.h"

int main(int argc, char *argv[])
{
int *p = NULL;
int a = 100;
MYDEBUG(INFO, F_CYAN, "hello world!");
HKPRT("hello world!\n");
HKPRTE("hello world!\n");
HKPRT("hello world! %s\n", "ifanhua");
HKPRTE("hello world! %s\n", "ifanhua");
return 0;
}

1.3 Makefile

点击查看详情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# .elf 可执行文件只能在已装Linux的开发板中运行
# .bin 可执行文件可以在未装Linux的开发板中运行
##======================================================##
TARGET := main
##======================================================##
## 工具设置
# 设置交叉编译器
ifeq ($(ARCH), arm)
CROSS_COMPILE := arm-linux-gnueabihf-
else
CROSS_COMPILE :=
endif
CC := $(CROSS_COMPILE)gcc
# 设置连接工具
LD := $(CROSS_COMPILE)ld
# 设置格式转换工具
OBJCOPY := $(CROSS_COMPILE)objcopy
# 设置反汇编工具
OBJDUMP := $(CROSS_COMPILE)objdump
# 设置删除符号表工具
OBJDUMP := $(CROSS_COMPILE)strip
# 设置addr2line工具
OBJDUMP := $(CROSS_COMPILE)addr2line
CFLAGS += -g
# NFS路径
NFS_ROOTFS := /home/hk/4nfs/imx_rootfs
NFS_DRIVER_DIR := $(NFS_ROOTFS)/01driver
# TFTP 相关路径
TFTP_SERVER_DIR := /home/hk/3tftp/imx6ull

##======================================================##
## 目录路径设置
# 头文件路径设置(.h)
INCDIRS := .

# 源文件路径设置(.S .C)
SRCDIRS := .

# 生成的中间 .o 文件的位置 (注意 链接脚本文件中定义了段的话也要注意修改路径)
OBJDIRS := .

# 链接脚本文件位置
LDSDIRS := .

##======================================================##
# 获取LDS链接脚本文件
LDSFILES := $(foreach dir, $(LDSDIRS), $(wildcard $(dir)/*.lds))
# 生成链接器的 -T file_name.lds 参数
LDSCFLAGS += $(patsubst %, -T %, $(LDSFILES))

# 生成编译器的 -I dir_path 参数
INCLUDE := $(patsubst %, -I %, $(INCDIRS))

# 获取所有的 .S 文件和 .C 文件名称(包含路径)
SFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S))
CFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))
# 获取所有的 .S 文件和 .C 文件名称(不包含路径)
SFILENDIR := $(notdir $(SFILES))
CFILENDIR := $(notdir $(CFILES))
# 指定所有的 .S 文件生成的 .o 文件路径及 生成的 .o 文件名(包含将要存放的路径)
SOBJS := $(patsubst %, $(OBJDIRS)/%, $(SFILENDIR:.S=.o))
COBJS := $(patsubst %, $(OBJDIRS)/%, $(CFILENDIR:.c=.o))
# 将所有要生成的 .o 文件合并到一个变量中(都包含路径)
OBJS := $(SOBJS) $(COBJS)

# 设置源文件路径
VPATH := $(SRCDIRS)

##======================================================##
## 生成可执行文件
# 生成目标文件
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) $^ -o $@


# 隐含规则推导相关中间文件
$(SOBJS) : $(OBJDIRS)/%.o : %.S
$(CC) $(CFLAGS) $(INCLUDE) $< -o $@

$(COBJS) : $(OBJDIRS)/%.o : %.c
$(CC) $(CFLAGS) $(INCLUDE) -c $< -o $@

install:
@sudo cp -vf $(TARGET) $(NFS_DRIVER_DIR)

uninstall:
@sudo rm -rvf $(NFS_DRIVER_DIR)/$(TARGET)

##======================================================##
## 清除文件
.PHONY: clean clean_o
# 删除所有编译链接生成的文件命令
clean: clean_o
@rm -vf $(TARGET)

# 删除所有的 .o 文件命令
clean_o:
@rm -vf *.o
##======================================================##
## 打印相关变量内容
printf:
@echo "LD = $(LD)"
@echo "INCDIRS = $(INCDIRS)"
@echo "SRCDIRS = $(SRCDIRS)"
@echo "INCLUDE = $(INCLUDE)"
@echo "LDSDIRS = $(LDSDIRS)"
@echo "SFILES = $(SFILES)"
@echo "CFILES = $(CFILES)"
@echo "LDSFILES = $(LDSFILES)"
@echo "SFILENDIR = $(SFILENDIR)"
@echo "CFILENDIR = $(CFILENDIR)"
@echo "SOBJS = $(SOBJS)"
@echo "COBJS = $(COBJS)"
@echo "OBJS = $(OBJS)"
@echo "VPATH = $(VPATH)"

1.4 最终效果

这里就懒得放图片了,大概打印的内容如下:

1
2
3
4
5
[INFO ][main.c:main:20]hello world!
[12-04 08:43:47][HK_LOG][main.c:21][main]hello world!
[12-04 08:43:47][HK_ERR][main.c:22][main]hello world!
[12-04 08:43:47][HK_LOG][main.c:23][main]hello world! ifanhua
[12-04 08:43:47][HK_ERR][main.c:24][main]hello world! ifanhua

2. 错误打印屏蔽

2.1 出现的问题

我们在写代码的时候会判断一个函数的返回值,若是执行出现错误,最好就是打印出错误信息,可是有些程序这里是一个循环,每次都会进,每次都会出现问题,那么打印就会一直刷,打印信息也是会占用资源的,狂刷的话,会影响我们其他功能,那怎么屏蔽呢?接下来来看一个实例吧。

2.2 打印屏蔽实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include <stdio.h>
#include <time.h>
#include <unistd.h>

typedef unsigned int (*PRT_FILTER_FUNC)(const char func[], unsigned int line, unsigned int filterTime);

extern unsigned int print_filter(const char func[], unsigned int line, unsigned int filterTime);
PRT_FILTER_FUNC errPrtFilter = print_filter;

#define PRT_MAX_PRT_LOC_CNT (32)
unsigned int gPrtLocSum[PRT_MAX_PRT_LOC_CNT] = {0};
unsigned long long gPrtLocTime[PRT_MAX_PRT_LOC_CNT] = {0};
unsigned int gPrtLogIdx = 0;
unsigned int errDebug = 1;

unsigned long long sys_pts_get_ms()
{
struct timespec tv;
unsigned long long lasttime = 0;
/*CLOCK_REALTIME:这种类型的时钟可以反映wall clocktime,用的是绝对时间*/
/*当系统的时钟源被改变,或者系统管理员重置了系统时间之后,这种类型的时钟可以得到相应的调整*/

/*CLOCK_MONOTONIC:用的是相对时间,他的时间是通过jiffies值来计算的*/
/*该时钟不受系统时钟源的影响,只受jiffies值的影响。*/

clock_gettime(CLOCK_MONOTONIC, &tv);
lasttime = tv.tv_sec * 1000 + tv.tv_nsec/(1000 * 1000);
return lasttime;
}

unsigned int print_filter(const char func[], unsigned int line, unsigned int filterTime)
{
unsigned long long curTime = 0, maxTime = 0;
unsigned int sum = 0;
unsigned int i = 0;
// 获取当前时间
curTime = sys_pts_get_ms();
//提取特征
while(func[i])
{
sum += func[i];
i++;
}
sum = ((sum & 0x1FFF) << 19) + ((line * i) & 0x7FFFF);
// 匹配最近一次打印
for(i = 0; i < PRT_MAX_PRT_LOC_CNT; i++)
{
if(gPrtLocSum[i] == sum && maxTime < gPrtLocTime[i])
{
maxTime = gPrtLocTime[i];
}
}
if(curTime > maxTime && (curTime - maxTime) < filterTime)
{
return 0; // 短时间内连续打印,需要屏蔽
}
gPrtLocSum[gPrtLogIdx] = sum;
gPrtLocTime[gPrtLogIdx] = curTime;
gPrtLogIdx = (gPrtLogIdx + 1) % PRT_MAX_PRT_LOC_CNT;
return 1;
}

#define PRTE(fmt...) \
do {\
if (errDebug) \
{ \
if(errPrtFilter && errPrtFilter(__FUNCTION__, __LINE__, 10000)) \
{ \
printf("[%s][%d]...", __FUNCTION__, __LINE__);\
printf(fmt); \
} \
} \
}while(0)

int main(int argc, char *argv[])
{
int i = 0;
while(1)
{
PRTE("i = %d\n", i);
printf("i = %d\n", i++);
i %= 100;
sleep(1);
}
return 0;
}

最终效果就是:

image-20241113231845220