extern关键字的一个妙用——为宏函数添加时间戳

extern和宏函数简介

extern简介

extern是C语言的一个关键字,用于修饰全局变量函数,它告诉编译器,当前编译单元(即.c文件)里引用的某个变量,并没有在本单元内部定义,而是在其他编译单元里定义,所以找不到变量定义的时候别报错,就当变量存在。至于生成可执行文件的时候咋办,这是链接器(linker)要操心的事,编译器不管。

举个例子

timer.c定义了一个全局变量g_sys_tick和中断处理程序Timer_ISR,用于记录系统当前已运行时间,每当Timer中断触发,g_sys_tick就会加一。

//timer.c
unsigned int   g_sys_tick;
void Timer_ISR (void)
{
  	  g_sys_tick ++;
}

uart.c想在打印日志时添加时间戳,就必须用extern声明该变量,然后在printf里引用。

//uart.h
extern unsigned int g_sys_tick;

//uart.c
#include "uart.h"
void operation() {
	printf("[%u] system panic!", g_sys_tick);
}

extern一般放在头文件里,不过放在.c文件里也行。

这种做法有个问题,一般日志都不会直接用printf,而是用宏函数实现简单的包装(例如添加时间戳),或者用专门的第三方库。

宏函数简介

宏函数也是一种C语言的,只不过它像函数一样支持用逗号隔开的参数列表

#define MAX(a, b) (a > b ? a : b)

printf("max num = %d \n", MAX(3, 5));

宏函数还有一种形式,用于将多条语句封装成一条语句:

//func.h
#define FUNC()		\
	do {					\
		func1();  \
		func2();	\
	} while (0)

//test.c
#include "func.h"
void operarion(){
	FUNC();
}

在上面的代码中,FUNC函数调用会被C语言的预处理器展开成do{} while(0)循环,每行末尾的反斜杠用于转义换行符(因为宏定义必须在一行内完成)。

这种奇怪的形式主要用于包装多条语句,因为相比下面这种,上面的可读性更好,同时相比正常while语句,不用担心会执行多次。

#define FUNC() func1();func2();

宏函数实现日志打印功能

很多不想引入太多第三方库的系统,特别是单片机系统,通常会这样实现日志打印函数

#define DEBUG(fmt, args...)		\
	do {					\
		printf("[%u] ",g_sys_tick);  \
		printf(fmt, ##args);	\
	} while (0)

DEBUG("spi query failed, second_counter: %d\r\n", second_counter);

第一条语句打印日志对应的事件发生时间,第二条语句打印具体的事件内容。这个宏函数编译会出错,报错信息是"找不到g_sys_tick的定义"之类。

为什么会这样?因为宏函数在某个c文件的某个函数内展开后,第一条printf访问的g_sys_tick并没有在对应的头文件中用extern声明。

怎么办?如果每个c文件开头都加一行

extern unsigned int g_sys_tick;

重复太多,不利于后续修改维护。

如果将其放到一个公共头文件里让所有c文件都包含,又污染了名称空间。

这时,我们可以求助于局部extern机制

局部extern来帮忙

局部extern简介

还是第一节那个例子,如果uart.coperation函数想要访问g_sys_tick,但又不想让c文件内的其他函数访问,这时就可以将extern从函数外部 挪到 函数内部

//uart.c
#include "uart.h"
void operation() {
	extern unsigned int g_sys_tick;  // 局部extern
	printf("[%u] system panic!", g_sys_tick);
}

上述代码能够编译通过并正常工作,亲测有效!

跟宏函数组合起来

于是我们可以将局部extern跟宏函数结合起来,造出宏日志函数

#define DEBUG(fmt, args...)		\
	do {					\
		extern unsigned int g_sys_tick; \
		printf("[%u] ",g_sys_tick);  \
		printf(fmt, ##args);	\
	} while (0)

上面这个宏函数,仅在调用它的函数内展开,也就只有那些函数能看到g_sys_tick,便于后续修改、维护,而且运行时开销为零。

调用也很简单

DEBUG("spi query failed, second_counter: %d\r\n", second_counter);

运行效果

[714] spi query failed, second_counter: 2
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值