0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

如何使用宏偷梁換柱

454398 ? 來源:alpha007 ? 作者:alpha007 ? 2022-11-15 17:33 ? 次閱讀

有些時(shí)候,一些原本的函數(shù)功能可能并不是我們想要的,于是就想著修改函數(shù),或者再封裝一層函數(shù)。

比如對(duì)一個(gè)函數(shù)包裝:

void func()

{

printf("hello/n");

}

// 包裝函數(shù)

void my_func()

{

printf("add/n");

func();

}

打印效果如下:

每打印一個(gè) hello 前面都會(huì)增加一個(gè) add。

這樣確實(shí)能達(dá)到效果,但是因?yàn)槎嗾{(diào)用了一次函數(shù),所以性能會(huì)部分下降,同時(shí)需要更大的??臻g,那么是否有一種更好的方式去達(dá)到相同的目的呢?

有的,那就是使用宏進(jìn)行偷梁換柱,達(dá)到貍貓換太子的目的。

我們以打印函數(shù)為例,對(duì)它進(jìn)行偷梁換柱。

一般的打印函數(shù)只會(huì)打印我們輸入給它的參數(shù),卻無(wú)法打印額外的信息,比如時(shí)間戳、函數(shù)名、文件名、行號(hào)等。簡(jiǎn)單一點(diǎn),假如我們希望在打印我們的消息前,能添加時(shí)間戳和函數(shù)名,又該如何做呢?

簡(jiǎn)單且易理解的偷梁換柱如下:

// 定義我們自己的打印函數(shù)格式

#define OSPREY_LOG(fmt, ...) rt_kprintf("<%08d>[%s] "fmt"/r/n",/

rt_tick_get(), __FUNCTION__, ##__VA_ARGS__)

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

// 重新定義,此時(shí)下面的所有 printf 是一個(gè)宏,而不是函數(shù)

#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 進(jìn)行打印,但自己比較懶,不想每次換平臺(tái)的時(shí)候都修改打印函數(shù)(比如 RT-Thread 使用 rt_kprintf 打?。蛘吲绿鎿Q的時(shí)候操作失誤,那么此時(shí)就可以使用這個(gè)技巧了。

我們先定義出我們自己的打印格式(關(guān)于這個(gè),魚鷹會(huì)專門寫一篇筆記介紹如何設(shè)計(jì)一個(gè)簡(jiǎn)單實(shí)用的日志打印框架,里面會(huì)詳細(xì)介紹這些內(nèi)容,目前暫時(shí)拿來用就行),這個(gè)格式包含了時(shí)間戳信息、函數(shù)名信息。

之后,使用 #undef 這條預(yù)編譯指令取消掉 printf 的作用域,接下來的代碼將不再調(diào)用標(biāo)準(zhǔn)庫(kù)的函數(shù),而是使用我們自己定義的宏函數(shù) printf,所以我們使用 #define 重新定義 printf。

也就是說,#undef printf 指令后面的代碼將使用 宏函數(shù) printf ,而不是標(biāo)準(zhǔn)庫(kù)函數(shù) printf,這是一道分水嶺。

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

可以看到,在我們的 “hello,Osprey”之前,打印了我們需要的時(shí)間戳和函數(shù)名信息,完美!

通過這些信息,我們就可以知道打印的消息是在什么時(shí)候打印的,又是在哪個(gè)函數(shù)中打印的,定位問題將更加方便(當(dāng)然你也可以加入文件名和行號(hào),看自己的需要了)。

通過以上三行代碼,我們成功且高效的完成了函數(shù)的再次封裝,并且除了這些代碼,不需要對(duì)后面的代碼做任何修改,萬(wàn)一平臺(tái)換了,也只需要修改這些代碼就行。

現(xiàn)在再來一個(gè)稍微難理解一點(diǎn)的。

既然前面的代碼可以替換 printf 打印函數(shù),那么我們會(huì)想,是否可以替換 rt_kprintf 本身呢?

也就是說本來我的代碼就是用 rt_kprintf 函數(shù)打印的,我們是否可以對(duì)它進(jìn)行封裝呢?
所以接下來的代碼應(yīng)運(yùn)而生:

// 定義我們自己的打印函數(shù)格式

#define OSPREY_LOG(fmt, ...) rt_kprintf("<%08d>[%s] "fmt"/r/n",/

rt_tick_get(), __FUNCTION__, ##__VA_ARGS__)

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

// 重新定義,此時(shí)下面的所有 rt_kprintf 是一個(gè)宏,而不是函數(shù)

#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);

}

}

當(dāng)你測(cè)試后,你會(huì)發(fā)現(xiàn),打印效果和前面的代碼等同,也就是說,通過三條代碼,成功將 rt_kprintf 貍貓換太子了。

其實(shí)當(dāng)你理解了 #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++)

因?yàn)?rt_kprintf 函數(shù)已經(jīng)有了,最后編譯、鏈接的時(shí)候也就能順利通過了,爽!

當(dāng)然,有時(shí)候版本發(fā)布的時(shí)候,我們發(fā)現(xiàn)不再需要打印函數(shù)了,那么我們只要使用如下方式即可消除打?。ㄎ募_頭添加即可,注意 //):

這個(gè)騷操作,你學(xué)會(huì)了嗎?


審核編輯 黃昊宇

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 函數(shù)
    +關(guān)注

    關(guān)注

    3

    文章

    4264

    瀏覽量

    62251
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    C語(yǔ)言定義使用技巧

    寫好C語(yǔ)言,漂亮的定義很重要,使用定義可以防止出錯(cuò),提高可移植性,可讀性,方便性等等。下面列舉一些成熟軟件中常用的定義。
    發(fā)表于 07-29 09:35 ?1085次閱讀

    C語(yǔ)言中的

    定義是我們C語(yǔ)言學(xué)習(xí)中非常重要的內(nèi)容。一些基礎(chǔ)的用法大家都比較清楚了,我們簡(jiǎn)單總結(jié)一下。1.定義的格式為:#define 標(biāo)識(shí)符 字符串。2.定義屬于預(yù)處理命令,在編譯過程中的預(yù)處理階段處理
    發(fā)表于 12-13 15:32

    offsetof與container_of詳解

    offsetof與container_of詳解 1.offsetof與container_of1.1、由結(jié)構(gòu)體指針進(jìn)而訪問各元素的原理通過結(jié)構(gòu)體整體變量來訪問其中各個(gè)元素,本
    發(fā)表于 10-13 16:35

    擴(kuò)展問題

    MPLAB IDIDV3.65和XC8HI,“擴(kuò)展工具”是一個(gè)方便的工具來查看在項(xiàng)目文件中的擴(kuò)展(右鍵單擊并選擇導(dǎo)航/視圖擴(kuò)展)。不幸的是,我發(fā)現(xiàn)了一個(gè)問題:當(dāng)我在文件中更改
    發(fā)表于 04-14 09:57

    什么是?

    什么是?示例的應(yīng)用
    發(fā)表于 12-15 07:34

    如何設(shè)計(jì)調(diào)試

    前言借調(diào)試的設(shè)計(jì),梳理下的用法重定向printf打印嵌入式設(shè)備基本會(huì)配置RS232串口作為調(diào)試IO接口,假設(shè)底層串口單字節(jié)輸出函數(shù)為SERIAL_PutChar(),利用fputc()和fputs()重定向printf函數(shù)void fputc(int byte, FI
    發(fā)表于 12-15 06:13

    C語(yǔ)言中的是什么

    第五章 性能優(yōu)化5.1 使用定義  在C語(yǔ)言中,是產(chǎn)生內(nèi)嵌代碼的唯一方法。對(duì)于嵌入式系統(tǒng)而言,為了能達(dá)到性能要求,是一種很好的代替函數(shù)的方法?! 懸粋€(gè)"標(biāo)準(zhǔn)"MIN ,這個(gè)
    發(fā)表于 12-15 08:20

    excel中的使用方法、技巧和步驟

    excel中的使用方法如下: 一、建立 二、執(zhí)行 三、編輯和刪除
    發(fā)表于 11-19 10:16 ?11.6w次閱讀
    excel中<b class='flag-5'>宏</b>的使用方法、技巧和步驟

    什么是,excel中的作用

    所謂,就是一些命令組織在一起,作為一個(gè)單獨(dú)命令完成一個(gè)特定任務(wù)。Microsoft Word中對(duì)定義為:“就是能組織到一起作為一獨(dú)立的命令使用的一系列word命令,它能使日常工作變得更容易”。Word使用
    發(fā)表于 11-19 10:36 ?5.5w次閱讀

    內(nèi)聯(lián)函數(shù)和定義的區(qū)別介紹

    定義是C語(yǔ)言提供的三種預(yù)處理功能的其中一種,這三種預(yù)處理包括:定義、文件包含、條件編譯。定義和操作符的區(qū)別是:定義是替換,不做計(jì)算,也不做表達(dá)式求解。
    發(fā)表于 12-15 15:33 ?2317次閱讀
    內(nèi)聯(lián)函數(shù)和<b class='flag-5'>宏</b>定義的區(qū)別介紹

    之剖析

    本講座將探討C預(yù)處理器及其預(yù)處理器處理。我們將探討一些較高級(jí)的示例,并且還會(huì)探討與有關(guān)的一些誤解,以及預(yù)處理器如何展開和對(duì)求值。這
    的頭像 發(fā)表于 06-07 13:46 ?2322次閱讀
    <b class='flag-5'>宏</b>之剖析

    當(dāng)使用參數(shù)調(diào)用時(shí),會(huì)將參數(shù)替換為主體

    在大多數(shù)定義示例中,每次出現(xiàn)的參數(shù)名稱都帶有括號(hào),并且另一對(duì)括號(hào)通常會(huì)包圍整個(gè)定義,這是編寫最好的方式。舉個(gè)例子
    的頭像 發(fā)表于 11-16 16:41 ?2209次閱讀

    AD637 SPICE模型

    AD637 SPICE模型
    發(fā)表于 06-17 13:43 ?12次下載
    AD637 SPICE<b class='flag-5'>宏</b>模型

    基于select!的進(jìn)階用法

    Tokio 是一個(gè)基于 Rust 語(yǔ)言的異步編程框架,它提供了一組工具和庫(kù),使得異步編程變得更加容易和高效。其中最重要的組件之一就是 select!。 select!是 Tokio 中的一個(gè)核心
    的頭像 發(fā)表于 09-19 15:35 ?554次閱讀

    如何規(guī)范和常量以及命名

    和常量 ◎ 定義和常量使用大寫字母或下劃線。 ◎ 用定義表達(dá)式時(shí),要使用完備的括號(hào),如下: #define HEHE_AREA( a , b ) (( a ) * ( b )) ◎
    的頭像 發(fā)表于 12-07 14:49 ?643次閱讀