本文主要是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.时间函数 在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; long tv_nsec; };
【返回值】 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() 函数的可重入版本,更加推荐使用。
【函数参数】
点击查看 struct tm 结构体成员
在使用 man 手册的时候,下边会有这个结构体成员的说明:
1 2 3 4 5 6 7 8 9 10 11 12 struct tm { int tm_sec; int tm_min; int tm_hour; int tm_mday; int tm_mon; int tm_year; int tm_wday; int tm_yday; int tm_isdst; };
【注意】要想得到实际的时间,那么年份需要加上 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());
这样我们就会得到这样的一个时间:
我们用于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 #define filename(x) (strrchr(x,'\\' )?(strrchr(x,'\\' ) + 1):x) #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[]) { 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 ; }
显示效果如下:
六、自用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; } 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/4 nfs/imx_rootfs NFS_DRIVER_DIR := $(NFS_ROOTFS)/01 driver # TFTP 相关路径 TFTP_SERVER_DIR := /home/hk/3 tftp/imx6ull ##======================================================## ## 目录路径设置 # 头文件路径设置(.h) INCDIRS := . # 源文件路径设置(.S .C) SRCDIRS := . # 生成的中间 .o 文件的位置 (注意 链接脚本文件中定义了段的话也要注意修改路径) OBJDIRS := . # 链接脚本文件位置 LDSDIRS := . ##======================================================## # 获取LDS链接脚本文件 LDSFILES := $(foreach dir, $(LDSDIRS), $(wildcard $(dir)
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_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 ; }
最终效果就是: