【骚操作】如何使用宏偷梁换柱?

有些时候,一些原本的函数功能可能并不是我们想要的,于是就想着修改函数,或者再封装一层函数。

比如对一个函数包装:

void func(){printf("hello\n");}
// 包装函数void my_func(){printf("add\n");  func();}

打印效果如下:

【骚操作】如何使用宏偷梁换柱?_第1张图片

每打印一个 hello 前面都会增加一个 add。

这样确实能达到效果,但是因为多调用了一次函数,所以性能会部分下降,同时需要更大的栈空间,那么是否有一种更好的方式去达到相同的目的呢?

有的,那就是使用进行偷梁换柱,达到狸猫换太子的目的。

我们以打印函数为例,对它进行偷梁换柱。

一般的打印函数只会打印我们输入给它的参数,却无法打印额外的信息,比如时间戳、函数名、文件名、行号等。简单一点,假如我们希望在打印我们的消息前,能添加时间戳和函数名,又该如何做呢?

简单且易理解的偷梁换柱如下:

// 定义我们自己的打印函数格式
#define OSPREY_LOG(fmt, ...)   rt_kprintf("<%08d>[%s] "fmt"\r\n",\
                    rt_tick_get(), __FUNCTION__, ##__VA_ARGS__)


#undef printf   // 使 printf 在下面失去效用


// 重新定义,此时下面的所有 printf 是一个宏,而不是函数 
#define printf(fmt, ...)  OSPREY_LOG(fmt, ##__VA_ARGS__)


void osprey_task(void *parameter)
{
uint32_t nbr = 0;
while(1)
    {
printf("hello, Osprey %u", nbr++);


        rt_thread_delay(1);  
    }            
}


原本我们的代码里面使用的是 printf 进行打印,但自己比较懒,不想每次换平台的时候都修改打印函数(比如RT-Thread 使用 rt_kprintf 打印),或者怕替换的时候操作失误,那么此时就可以使用这个技巧了。

我们先定义出我们自己的打印格式(关于这个,鱼鹰会专门写一篇笔记介绍如何设计一个简单实用的日志打印框架,里面会详细介绍这些内容,目前暂时拿来用就行),这个格式包含了时间戳信息、函数名信息。

之后,使用 #undef 这条预编译指令取消掉 printf 的作用域,接下来的代码将不再调用标准库的函数,而是使用我们自己定义的宏函数 printf,所以我们使用 #define重新定义 printf。

也就是说,#undef printf 指令后面的代码将使用 宏函数 printf ,而不是标准库函数 printf,这是一道分水岭。

接下来看看打印的效果如何:

【骚操作】如何使用宏偷梁换柱?_第2张图片

可以看到,在我们的 “hello,Osprey”之前,打印了我们需要的时间戳和函数名信息,完美!

通过这些信息,我们就可以知道打印的消息是在什么时候打印的,又是在哪个函数中打印的,定位问题将更加方便(当然你也可以加入文件名和行号,看自己的需要了)。

通过以上三行代码,我们成功且高效的完成了函数的再次封装,并且除了这些代码,不需要对后面的代码做任何修改,万一平台换了,也只需要修改这些代码就行。

现在再来一个稍微难理解一点的。

既然前面的代码可以替换printf打印函数,那么我们会想,是否可以替换rt_kprintf 本身呢?

也就是说本来我的代码就是用 rt_kprintf 函数打印的,我们是否可以对它进行封装呢?

所以接下来的代码应运而生:

// 定义我们自己的打印函数格式
#define OSPREY_LOG(fmt, ...)   rt_kprintf("<%08d>[%s] "fmt"\r\n",\
                    rt_tick_get(), __FUNCTION__, ##__VA_ARGS__)


#undef rt_kprintf   // 使 rt_kprintf 在下面失去效用


// 重新定义,此时下面的所有 rt_kprintf 是一个宏,而不是函数 
#define rt_kprintf(fmt, ...)  OSPREY_LOG(fmt, ##__VA_ARGS__)


void osprey_task(void *parameter)
{
uint32_t nbr = 0;
while(1)
    {
        rt_kprintf("hello, Osprey %u", nbr++);


        rt_thread_delay(1);  
    }            
}

图片版:

【骚操作】如何使用宏偷梁换柱?_第3张图片

当你测试后,你会发现,打印效果和前面的代码等同,也就是说,通过三条代码,成功将 rt_kprintf 狸猫换太子了。

其实当你理解了 #undef 和 #define,上面代码是不难理解的,#undef 取消了 rt_kprintf 的定义,而  #define 又重新定义了 rt_kprintf,所以接下来的:

rt_kprintf("hello, Osprey %u", nbr++);

被替换成了 :

rt_kprintf("<%08d>[%s] "hello, Osprey %u"\r\n",\rt_tick_get(), __FUNCTION__, nbr++)

因为 rt_kprintf函数已经有了,最后编译、链接的时候也就能顺利通过了,爽!

当然,有时候版本发布的时候,我们发现不再需要打印函数了,那么我们只要使用如下方式即可消除打印(文件开头添加即可,注意 //):

【骚操作】如何使用宏偷梁换柱?_第4张图片这个骚操作,你学会了吗?

1.嵌入式系统中,哪些应用正被重点关注?

2.听嵌入式大牛讲解硬核单片机编程思想!

3.软件神器TortoiseGit,让你优雅管理单片机程序版本!

4.TrustZone for Armv8-M和 TrustZone是什么关系?

5.外专业“入坑”嵌入式的开心成长记!

6.STM32中的位带操作,用好了让代码更简洁!

免责声明:本文系网络转载,版权归原作者所有。如涉及作品版权问题,请与我们联系,我们将根据您提供的版权证明材料确认版权并支付稿酬或者删除内容。

你可能感兴趣的:(单片机,嵌入式,java,python,c++)