很多人在用 printf 函數(shù)進(jìn)行串口打印的時候,都會被告知需要重定向 fputc 函數(shù)(別的平臺可能不是這個函數(shù)),讓字符串?dāng)?shù)據(jù)輸出到指定串口,按照網(wǎng)上的教程也能很快解決。但是卻沒人告訴你為什么可以被重定向,為什么明明使用的是 printf 函數(shù),重定向的卻是 fputc 函數(shù)?
使用 51 的時候,我們也可以使用 printf 函數(shù),但是我們并沒有進(jìn)行重定向,也能使用,這又是為什么?
對于經(jīng)驗豐富的人來說,這些問題心里應(yīng)該都有答案,但考慮到有一些道友可能并不了解,所以今天就稍微水那么一篇吧。
這些問題從大一到大三,一直困惑著魚鷹,直到魚鷹在代碼中看到這么個東西:
__attribute__((weak)) // 注意兩個括號
魚鷹一看,沒見過啊,不懂啊,所以魚鷹趕緊去網(wǎng)上查了一下,不查不要緊,一查嚇一跳,發(fā)現(xiàn) __attribute__ 這個東東了不得啊,很多C語言屬性都能修改,功能實在是太強大了,強大到魚鷹自認(rèn)為掌握得不錯的C語言都還只是基礎(chǔ),也就只配在小白面前嘚瑟一下。
言歸正傳,為了突出重點,今天只講 weak 屬性,以防分心。
我們都知道,函數(shù)名不可以重名,當(dāng)然不同文件內(nèi)聲明的 static 函數(shù)倒是可以解除該限制(可查看《C語言之static》)。
但是如果沒有使用 static ,那么編譯器就會給你報錯,告訴你函數(shù)名重復(fù)咯。
編譯器一共告訴你兩個信息:
1、重復(fù)的函數(shù)名是 func_name
2、重復(fù)的地方在 board.c 和 main.c 文件里面(后面 .o 表示目標(biāo)文件,由對應(yīng)的 .c 文件生成)
編譯器一發(fā)出這樣的信息,程序員很快就能找到問題并解決,所以看懂編譯信息很重要(如果有些編譯信息不常見,復(fù)制這條信息到網(wǎng)上一搜,一大堆文章就冒出來了)。
根據(jù)錯誤信息,只要修改一處變量名,即可消除該錯誤。
那么這個和printf重定向有什么關(guān)系?
我們知道,printf 最終會調(diào)用 fputc 進(jìn)行字符串輸出,但是這些函數(shù)是標(biāo)準(zhǔn)庫提供的,而標(biāo)準(zhǔn)庫沒有提供源碼給你,當(dāng)你需要用的時候添加 <stdio.h> 即可。
但是很多時候,fputc 輸出的位置可能需要改變,比如輸出到 LCD、串口1、串口2,我們總不可能去修改標(biāo)準(zhǔn)庫的源碼吧,但也沒有源碼提供啊,怎么才能在不修改源碼的情況下滿足這個需求呢?
方法是有的,比如你可以通過某個函數(shù)向printf中注冊一個回調(diào)函數(shù),讓printf調(diào)用這個回調(diào)函數(shù)進(jìn)行字符串輸出即可,但是標(biāo)準(zhǔn)庫并沒有提供這個東西,因為它用了更好的方式解決這個問題。
那就是本文的主角,符號屬性弱化,weak。
假設(shè)我們拿到了標(biāo)準(zhǔn)庫的源碼,能清楚的看到實現(xiàn)原理,那么我們應(yīng)該能看到一個fputc的函數(shù)。然后你通過分析原理,發(fā)現(xiàn)它的輸出位置不是自己想要的,那么你會怎么做?
既然有源碼,好辦,直接修改fputc的實現(xiàn)即可,簡單。
但現(xiàn)在沒有源碼,怎么辦?但你發(fā)現(xiàn)你在自己的源文件里面直接實現(xiàn)fputc函數(shù)好像也沒事,為什么?就是因為標(biāo)準(zhǔn)庫將fputc函數(shù)的屬性進(jìn)行了弱化,即:
這樣做有什么好處?
1、別人可以不需要給你源碼
2、即使沒有源碼,也能間接的達(dá)到修改源碼的目的
3、即使有源碼,通過該屬性設(shè)置,也不需要刪除別人的代碼去重新實現(xiàn),可以保留原來的代碼。
4、不需要使用回調(diào)函數(shù)的方式進(jìn)行注冊,可以直接重新實現(xiàn)該函數(shù),非常簡單
5、存在一個默認(rèn)函數(shù)實現(xiàn),如果說你不想重新實現(xiàn)函數(shù),那么編譯器就會使用該函數(shù)進(jìn)行編譯、鏈接,而不會在編譯時出現(xiàn)錯誤或警告(這就是為什么即使你沒有重新寫一個fputc,編譯也不會報錯的原因)
當(dāng)然,第五點,既是好處也是壞處,因為編譯器沒有提示,你就不知道你到底有沒有重新實現(xiàn)函數(shù)了。
其實要查看編譯器鏈接的到底是哪一個函數(shù)很簡單,打開map文件(關(guān)注了這么久,怎么打開的就不多說了),搜索對應(yīng)的函數(shù)名即可:
你會發(fā)現(xiàn),雖然 main.c 文件中雖然也有一個 func_name 函數(shù),但實際上,編譯器鏈接的是 board.c 文件的,原因就是因為 main.c 文件的 func_name 函數(shù)屬性被弱化了。
這樣一來,即使函數(shù) func_name 在 main() 函數(shù)中被調(diào)用,但它沒有使用本文件的func_name,而是調(diào)用了 board.c 文件中的函數(shù)。
但你將 board.c 文件中的函數(shù)刪掉后,編譯后并不會出現(xiàn)錯誤,并且會發(fā)現(xiàn) main() 調(diào)用的函數(shù)變成了 main.c 文件中的函數(shù)。
就是這么奇妙!
事實上,這個屬性弱化不僅僅在 printf 函數(shù)中體現(xiàn)了,在中斷處理函數(shù)中也做了這樣的處理,只不過這是匯編方式:
這就是為什么你可以在任何文件內(nèi)寫中斷處理函數(shù),而即使你沒有寫中斷處理函數(shù),編譯器也不會報錯的原因!
當(dāng)然了,這種屬性設(shè)置雖然可以保證不同文件的函數(shù)名可以相同(同一個文件的函數(shù)名還是不可以相同),但是最終只有一個函數(shù)會被編譯器所鏈接!
哦,對了,如果你要實現(xiàn)多個不同的串口打印輸出,不如使用 vsprintf(建議 vsnprintf),好處就是這個函數(shù)的輸出位置不是fputc,而是你給定的緩存空間,這樣你就可以實現(xiàn)自己的printf函數(shù)了。
來源:公眾號【魚鷹談單片機】
作者:魚鷹Osprey
ID :emOsprey
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!