99精品伊人亚洲|最近国产中文炮友|九草在线视频支援|AV网站大全最新|美女黄片免费观看|国产精品资源视频|精彩无码视频一区|91大神在线后入|伊人终合在线播放|久草综合久久中文

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

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

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

弱符號(hào)的作用與示例

科技綠洲 ? 來(lái)源:嵌入式大雜燴 ? 作者:嵌入式大雜燴 ? 2023-06-22 11:36 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

弱符號(hào)

弱符號(hào)是指在定義或者聲明一個(gè)對(duì)象(變量、結(jié)構(gòu)體成員、函數(shù))時(shí),在對(duì)象的前面添加 attribute ((weak)) 標(biāo)志所得到的對(duì)象符號(hào)。如下所示函數(shù)即為一個(gè)弱對(duì)象符號(hào) void test_weak_attr(void),或者稱該函數(shù)是弱函數(shù)屬性的、虛函數(shù)。

__attribute__((weak)) void test_weak_attr(void)
// 或者使用如下樣式的定義,兩者等效
void __attribute__((weak)) test_weak_attr(void)
{
    printf("Weak Func!\\r\\n");
}

弱符號(hào)的作用與示例

弱符號(hào)是相對(duì)于強(qiáng)符號(hào)而言的,在定義或者聲明變量、函數(shù)時(shí),未添加 attribute ((weak)) 標(biāo)識(shí)的就默認(rèn)為強(qiáng)符號(hào)。如下,最普通的函數(shù)定義,就是定義了一個(gè)強(qiáng)符號(hào) void test_strong_ref(void):

void test_weak_attr(void)
{
    printf("this is a strong func\\r\\n");
}

驅(qū)動(dòng)程序往往需要考慮兼容性,因?yàn)橐嫒魏芏鄰S商的不同型號(hào)的設(shè)備。若驅(qū)動(dòng)程序中使用強(qiáng)符號(hào)定義一些與適配的設(shè)備的特性相關(guān)的功能,則下次適配其他設(shè)備時(shí),該強(qiáng)符號(hào)函數(shù)可能需要被修改,以兼容新的設(shè)備。當(dāng)適配的設(shè)備很多時(shí),頻繁地更改驅(qū)動(dòng)代碼將破壞驅(qū)動(dòng)的可維護(hù)性。

弱符號(hào)的出現(xiàn)可以很好地解決該問(wèn)題。弱符號(hào)的對(duì)象具有可以被重定義的功能(即可以被重載)。下面通過(guò)測(cè)試說(shuō)明弱符號(hào)這種可被重載的特性。

在 test_weak_attr.c 程序中定義如下弱函數(shù):

// test_weak_attr.c
#include < stdio.h >

__attribute__((weak)) void test_weak_attr(void)
{
    printf("this is a weak func\\r\\n");
}

在 main.c 中定義如下程序:

// main.c
void test_weak_attr(void)
{
    printf("this is a strong func\\r\\n");
}

void app_main(void)
{
    printf("init done\\r\\n");

    test_weak_attr();
}

編譯運(yùn)行該 main.c 程序,得到的結(jié)果是什么樣子的呢?

將 main.c 中的 void test_weak_attr(void) 函數(shù)注釋掉,再重新編譯運(yùn)行程序得到的結(jié)果是:

小結(jié):在使用弱符號(hào)函數(shù)時(shí),我們可以重新定義一個(gè)同名的強(qiáng)符號(hào)函數(shù)來(lái)替代它;若沒(méi)有重新定義一個(gè)強(qiáng)函數(shù)來(lái)替換它,就使用弱函數(shù)的實(shí)現(xiàn)。弱函數(shù)就好像是一個(gè)可以被替換的“默認(rèn)函數(shù)”。

值得一提的是,舊版本的編譯器還可以使用如下方式的定義(僅聲明無(wú)效)將一個(gè)對(duì)象定義為一個(gè)弱對(duì)象:

linux 的一些代碼中,__weak 其實(shí)就是通過(guò) attribute ((weak))的重命名,兩者等效。

弱引用

弱引用是在聲明一個(gè)對(duì)象時(shí),通過(guò)__attribute__ ((weakref()) 定義一個(gè)符號(hào)的引用關(guān)系。如下所示即定義 test_weakref() 函數(shù)弱引用 test_weak_ref() 函數(shù)。

弱引用是相對(duì)于強(qiáng)引用而言的。未通過(guò) attribute ((weakref()) 的符號(hào)和實(shí)現(xiàn)代碼之間的關(guān)系是強(qiáng)引用。如下即為一個(gè)強(qiáng)引用函數(shù)。它直接給出了 函數(shù) test_strong_ref(void) 的實(shí)現(xiàn)。

在編譯程序的時(shí)候,我們可以直接使用 test_strong_ref(void) 而不必?fù)?dān)心編譯不通過(guò)。如果,我沒(méi)有時(shí)間去實(shí)現(xiàn) test_strong_ref(void) ,還想在程序里先使用該函數(shù)那該如何呢?(是的,就是想白嫖,不想實(shí)現(xiàn),還想先在程序里使用這個(gè)函數(shù))。

這個(gè)時(shí)候弱引用就派上用場(chǎng)了??梢韵葘⒃摵瘮?shù)定義為弱引用插入到代碼中,待后期有時(shí)間再慢慢優(yōu)化代碼實(shí)現(xiàn)這個(gè)函數(shù)完整的功能。下面結(jié)合測(cè)試進(jìn)行說(shuō)明。

測(cè)試代碼1:

static void test_weakref(void) __attribute__ ((weakref("test_weak_ref")));
void app_main(void)
{
    printf("init done\\r\\n");
    if (test_weakref) {
        test_weakref();
    } else {
        printf("There is no weakref\\r\\n");
    }
}

測(cè)試結(jié)果:

There is no weakref

測(cè)試代碼2:

void test_weak_ref(void)
{
    printf("this is a weak ref\\n");
}
static void test_weakref(void) __attribute__ ((weakref("test_weak_ref")));
void app_main(void)
{
    printf("init done\\r\\n");
    if (test_weakref) {
        test_weakref();
    } else {
        printf("There is no weakref\\r\\n");
    }
}

測(cè)試結(jié)果:

this is a weak ref

小結(jié):強(qiáng)引用,在未定義該強(qiáng)引用的實(shí)現(xiàn)時(shí),編譯會(huì)報(bào)錯(cuò)誤:未定義的引用。弱引用允許定義一個(gè)未實(shí)現(xiàn)(未實(shí)例化)的對(duì)象,這在編譯的時(shí)候會(huì)將該對(duì)象處理成 NULL,編譯器并不會(huì)報(bào)錯(cuò)。通過(guò)使用弱引用可以實(shí)現(xiàn)后期優(yōu)化代碼的功能。而避免改動(dòng)使用該函數(shù)的地方。使用弱函數(shù)可以實(shí)現(xiàn)類似“鉤子(hook)"函數(shù)的功能。

實(shí)際上,包括C、python、go 編程語(yǔ)言在內(nèi)的很多語(yǔ)言 都有類似用法,本篇文章敘述的方法同樣適用于這些語(yǔ)言的相關(guān)開(kāi)發(fā)。

注意:弱引用僅在靜態(tài)編譯中有效,動(dòng)態(tài)鏈接中可能無(wú)效。

總結(jié)

弱符號(hào)、弱引用都是增強(qiáng)程序的可維護(hù)性的方法。弱符號(hào)通過(guò)可以被重定義的特性,實(shí)現(xiàn)可以被替換實(shí)現(xiàn)。弱引用通過(guò)可以暫時(shí)使用一個(gè)未定義的函數(shù)的功能,實(shí)現(xiàn)允許后期再實(shí)現(xiàn)該函數(shù)具體功能,而不必?fù)?dān)心編譯不通過(guò)。

為了方便理解,我們先預(yù)設(shè)一個(gè)應(yīng)用場(chǎng)景:

我們編寫(xiě)了一個(gè)模擬IIC的驅(qū)動(dòng),希望它能夠在不同的的平臺(tái)運(yùn)行,目標(biāo)的平臺(tái)就設(shè)為 stm32 標(biāo)準(zhǔn)庫(kù),stm32 HAL 庫(kù),stm32 LL 庫(kù),和 RT-Thread Driver 驅(qū)動(dòng)庫(kù)。

或許讀者有疑惑,為什么同樣是 stm32 ,卻分成三個(gè)平臺(tái)呢?這是因?yàn)閺目缙脚_(tái)軟件編寫(xiě)者的角度看,只要調(diào)用的庫(kù)的 API 不一致,就和換一個(gè)不同的平臺(tái)沒(méi)有什么本質(zhì)的差別,如果在代碼中寫(xiě)死了 API 的調(diào)用,即使是同一個(gè)平臺(tái),仍然像多平臺(tái)一樣不能運(yùn)行。

由此可以看出,跨平臺(tái)的困難所在,也不是由硬件平臺(tái)所導(dǎo)致的,而是由代碼所依賴的 API 的不同導(dǎo)致的。同一個(gè)平臺(tái),如果依賴的 API 不同,代碼就不能跨平臺(tái),同樣地,不同的平臺(tái),如果依賴的 API 相同,也可以跨平臺(tái)。

所以歸根結(jié)底,是代碼所依賴的 API 出現(xiàn)了不同,所以下文中所說(shuō)的“平臺(tái)”,實(shí)際上對(duì)應(yīng)的是一套 API 。

我們繼續(xù)說(shuō)這個(gè)模擬 IIC 的驅(qū)動(dòng),模擬 IIC 驅(qū)動(dòng)是使用 GPIO 的反轉(zhuǎn)來(lái)模擬 IIC 協(xié)議,所以依賴了平臺(tái)的 GPIO API,如果把調(diào)用 GPIO 的部分寫(xiě)死,那么換一個(gè)平臺(tái),就肯定不能在多個(gè)平臺(tái)上運(yùn)行。

下面我們開(kāi)始討論在多平臺(tái)運(yùn)行的解決方案。

我們先從最樸素簡(jiǎn)單的解決方案開(kāi)始,然后逐步迭代到弱函數(shù)的方案,這樣有兩方面好處:

一是從簡(jiǎn)單的方案開(kāi)始,循序漸進(jìn)地介紹,可以降低閱讀門檻。

二是可以帶讀者親歷一遍方案的演進(jìn)過(guò)程,以及演進(jìn)動(dòng)因。

和給直接給結(jié)果相比,注重過(guò)程和動(dòng)因更能夠還原技術(shù)決策的真實(shí)過(guò)程。因?yàn)槿魏渭夹g(shù)都是從簡(jiǎn)單樸素逐步演進(jìn)而來(lái)的,如果直接給出最后的結(jié)果,會(huì)產(chǎn)生理解的斷層,即使記住了幾種技術(shù)的優(yōu)劣,在新的場(chǎng)景中,面對(duì)更加多樣化的實(shí)際問(wèn)題也會(huì)難免乏力。

先從最樸素的方案講起。

方案一、手動(dòng)控制添加編譯的 .c 文件

樸素的方案有很多,比如就是多搞幾個(gè)版本的 .c 文件,比如SIMU_IIC_STM32_HAL.c ,SIMU_IIC_STM32_LL.c, SIMU_IIC_RTT.c 需要哪個(gè)就添加哪個(gè)進(jìn)去編譯不就完了嘛!

這種樸素的方案雖然看起來(lái)簡(jiǎn)單,但是,這幾個(gè)文件中包含有共用的邏輯,例如模擬 IIC 的協(xié)議的實(shí)現(xiàn),如何將 8bit 的數(shù)據(jù)依次發(fā)送,等等。

這些共用的邏輯,相當(dāng)于在每個(gè)文件中都復(fù)制了一份,一旦修改到共用的邏輯,就要手動(dòng)同步每個(gè)文件,這會(huì)導(dǎo)致代碼冗余和維護(hù)難度的急劇增加,很容易出現(xiàn)人為失誤。如果需要添加更多的平臺(tái)支持,就需要再次復(fù)制修改代碼。

另一個(gè)問(wèn)題是,使用不同的編譯工具鏈,添加編譯文件的方式并不一樣,例如,Visual Studio 和 MDK keil 通常是手動(dòng)添加,而 CMake 通常直接添加目錄或者通過(guò)文件后綴進(jìn)行搜索。用戶在使用不同的編譯工具時(shí),需要針對(duì)編譯工具來(lái)分別處理,這也增加了維護(hù)的成本。

因此,我們需要尋找更加優(yōu)雅的解決方案。

方案一中最核心的問(wèn)題,是沒(méi)有分離共用的邏輯,和各個(gè)平臺(tái)的適配接口

在通常的命名慣例中,共用的邏輯稱為 Common,而各個(gè)平臺(tái)的適配接口稱為 Port。

在接下來(lái)的方案中,我們就會(huì)引入 Common 和 Port 分離的設(shè)計(jì)思想,Common 和 Port 的分離,使得對(duì)共用邏輯的修改和增強(qiáng)直接 “分發(fā)” 到了各個(gè)的 Port 中,而增添新的平臺(tái),不需要對(duì) Common 做任何修改。

方案二、條件編譯

一種更加優(yōu)雅的解決方案是使用條件編譯。條件編譯是一種編譯時(shí)根據(jù)條件選擇編譯代碼的技術(shù),可以通過(guò)編譯器提供的宏定義和預(yù)處理指令來(lái)實(shí)現(xiàn)。

在我們的模擬 IIC 驅(qū)動(dòng)中,可以直接編寫(xiě) Common 部分,然后 Common 部分通過(guò)條件編譯,可以根據(jù)平臺(tái)的不同選擇不同的 GPIO Port API。

例如,在 STM32 標(biāo)準(zhǔn)庫(kù)中,可以使用 GPIO_SetPinMode 和 GPIO_WritePin 接口來(lái)模擬 IIC 協(xié)議,而在 STM32 HAL 庫(kù)中,可以使用 HAL_GPIO_WritePin 和 HAL_GPIO_ReadPin 接口來(lái)模擬 IIC 協(xié)議。因此,在代碼中可以使用如下的條件編譯方式:

#if defined (USE_STM32_STD_LIB)
    /* STM32 Standard Peripheral Library */
    GPIO_SetPinMode(SDA_PORT, SDA_PIN, GPIO_MODE_OUTPUT_PP);
    GPIO_SetPinMode(SCL_PORT, SCL_PIN, GPIO_MODE_OUTPUT_PP);
    ...
#elif defined (USE_STM32_HAL_LIB)
    /* STM32 HAL Library */
    HAL_GPIO_WritePin(SDA_PORT, SDA_PIN, GPIO_PIN_SET);
    HAL_GPIO_WritePin(SCL_PORT, SCL_PIN, GPIO_PIN_SET);
    ...
#elif defined (USE_STM32_LL_LIB)
    /* STM32 LL Library */
    LL_GPIO_SetOutputPin(SDA_PORT, SDA_PIN);
    LL_GPIO_SetOutputPin(SCL_PORT, SCL_PIN);
    ...
#elif defined (USE_RTT_DRIVER)
    /* RT-Thread Driver */
    rt_pin_mode(SDA_PIN, PIN_MODE_OUTPUT);
    rt_pin_mode(SCL_PIN, PIN_MODE_OUTPUT);
    ...
#endif

這樣,在編譯代碼時(shí),我們可以通過(guò)宏定義來(lái)選擇編譯使用哪個(gè)平臺(tái)的代碼,

從而實(shí)現(xiàn)跨平臺(tái)運(yùn)行。

然而,這種條件編譯方式還是有一些問(wèn)題,如果我們需要添加新的平臺(tái)支持,就需要添加新的宏定義和條件編譯,而且需要修改模塊的源碼。

方案三 函數(shù)指針

我們可以進(jìn)一步分離 Common 和 Port,將其放在不同的 .c 文件中,Common 通過(guò)函數(shù)調(diào)用的方式來(lái)訪問(wèn) Port 提供的接口,這樣可以更加靈活和方便地添加新的平臺(tái)支持。

具體實(shí)現(xiàn)可以通過(guò)在 Common 中定義一些函數(shù)指針類型來(lái)實(shí)現(xiàn)。

例如,我們可以定義一個(gè)名為 IICOps 的結(jié)構(gòu)體,其中包含了一些指向函數(shù)的指針,這些函數(shù)實(shí)現(xiàn)了具體的 IIC 操作。

在 Port 中,我們實(shí)現(xiàn)這些函數(shù),并將其指針傳遞給 Common 中的 IICOps 結(jié)構(gòu)體。

這樣,在 Common 中就可以通過(guò)調(diào)用這些函數(shù)指針來(lái)訪問(wèn) Port 提供的接口了。

這種方案的好處是,添加新的平臺(tái)支持時(shí),只需要實(shí)現(xiàn)相應(yīng)的 Port 函數(shù),并將其指針傳遞給 Common 中的結(jié)構(gòu)體即可,不需要修改 Common 的源碼。

同時(shí),由于 Common 和 Port 分離,不同平臺(tái)的適配代碼可以相互獨(dú)立,修改一方不會(huì)影響到另一方,從而減少了代碼冗余和維護(hù)難度。

但是,使用函數(shù)指針有兩個(gè)主要的缺點(diǎn),一是函數(shù)指針本身需要占據(jù)內(nèi)存,增加了內(nèi)存的開(kāi)銷,二是函數(shù)指針需要在初始化時(shí)進(jìn)行加載。

具體來(lái)說(shuō),函數(shù)指針的內(nèi)存開(kāi)銷相對(duì)于代碼本身并不大,通常可以忽略不計(jì),但在嵌入式系統(tǒng)中,資源有限,內(nèi)存開(kāi)銷需要更加注意。

而函數(shù)指針的初始化需要在程序啟動(dòng)時(shí)進(jìn)行,這也會(huì)對(duì)程序的啟動(dòng)時(shí)間產(chǎn)生一定的影響。此外,函數(shù)指針的使用可能會(huì)導(dǎo)致一定的運(yùn)行時(shí)開(kāi)銷,需要在程序性能和資源利用方面做出權(quán)衡。

另外,函數(shù)指針的使用需要程序員有一定的技術(shù)水平和經(jīng)驗(yàn),需要合理地進(jìn)行函數(shù)指針的定義、傳遞和調(diào)用等操作,避免出現(xiàn)潛在的錯(cuò)誤和安全問(wèn)題。

總的來(lái)說(shuō),函數(shù)指針是一種比較靈活和方便的方式,可以幫助我們實(shí)現(xiàn)代碼的跨平臺(tái)支持,但需要在實(shí)際應(yīng)用中仔細(xì)考慮其使用場(chǎng)景和影響,做出合理的決策。

方案四、Common 中聲明,Prot 中實(shí)現(xiàn)

在這種方案中,我們?nèi)匀粚?Common 和 Port 分離,但是我們不再使用函數(shù)指針來(lái)訪問(wèn) Port 中的接口,而是將其定義為 extern 聲明,由 Port 來(lái)實(shí)現(xiàn)具體的函數(shù)。

具體實(shí)現(xiàn)可以通過(guò)在 Common 中定義一些 extern 聲明的函數(shù),這些函數(shù)實(shí)現(xiàn)了具體的 IIC 操作,但是并不在 Common 中實(shí)現(xiàn)具體的代碼邏輯,而是在 Port 中實(shí)現(xiàn)。

在 Port 中,我們實(shí)現(xiàn)這些函數(shù),并將其聲明為 extern,然后在編譯時(shí)鏈接到 Common 中。

這樣,在 Common 中就可以通過(guò)調(diào)用這些函數(shù)來(lái)訪問(wèn) Port 提供的接口了。

這種方案的好處是,添加新的平臺(tái)支持時(shí),只需要實(shí)現(xiàn)相應(yīng)的 Port 函數(shù),并在編譯時(shí)鏈接到 Common 中即可,不需要修改 Common 的源碼。

這樣 Port 的實(shí)現(xiàn)函數(shù)的掛載就提前到了編譯階段,避免了運(yùn)行時(shí)掛載函數(shù)指針的復(fù)雜性和容易出錯(cuò)問(wèn)題。以及避免了函數(shù)指針的內(nèi)存占用。

但是,和 IIC 的例子不同的是,在一些更實(shí)際的項(xiàng)目中,隨著軟件復(fù)雜度的提升, Common 中使用的 Port 函數(shù)數(shù)量會(huì)快速膨脹,這時(shí),每個(gè) Port 函數(shù)都必須要實(shí)現(xiàn),即使這個(gè)功能非常的冷門,這樣 Common 中每增加一個(gè) Port 的依賴,都要求所有的 Port 進(jìn)行及時(shí)的跟進(jìn),否則整個(gè)項(xiàng)目都無(wú)法編譯通過(guò)。

接下來(lái),就要有請(qǐng) weak (弱函數(shù))機(jī)制出馬了

但方案四的缺點(diǎn)在于,在一些更實(shí)際的項(xiàng)目中,隨著軟件復(fù)雜度的提升,Common 中使用的 Port 函數(shù)數(shù)量會(huì)快速膨脹,這時(shí),每個(gè) Port 函數(shù)都必須要實(shí)現(xiàn),即使這個(gè)功能非常的冷門,這樣 Common 中每增加一個(gè) Port 的依賴,都要求所有的 Port 進(jìn)行及時(shí)的跟進(jìn),否則整個(gè)項(xiàng)目都無(wú)法編譯通過(guò)。

方案五 弱函數(shù)

為了解決這個(gè)問(wèn)題,可以使用 weak (弱函數(shù))機(jī)制,將所有的 Port 函數(shù)都定義為 weak 函數(shù)。

weak 函數(shù)是一種特殊的函數(shù)類型,帶 weak 的函數(shù)和不帶 weak 的函數(shù)可以同時(shí)存在,如果有不帶 weak 的函數(shù),就會(huì)優(yōu)先鏈接不帶 weak 的實(shí)現(xiàn),如果沒(méi)有找到不帶 weak 的實(shí)現(xiàn)函數(shù),就會(huì)使用 weak 函數(shù)作為默認(rèn)的實(shí)現(xiàn)。

即,在使用弱函數(shù)時(shí),如果找到多個(gè)實(shí)現(xiàn),鏈接器會(huì)選擇優(yōu)先級(jí)最高的實(shí)現(xiàn)。

在 C 語(yǔ)言中,可以使用 attribute((weak)) 來(lái)聲明一個(gè)函數(shù)為弱函數(shù)。例如:

attribute((weak)) void port_func()
{
    // 默認(rèn)實(shí)現(xiàn)
}
attribute((weak)) void port_func()

而在 Port 中,只需要實(shí)現(xiàn)需要的函數(shù)即可,如果某些函數(shù)不需要實(shí)現(xiàn),可以不用管它,因?yàn)樵阪溄訒r(shí)會(huì)使用 Common 中的默認(rèn)實(shí)現(xiàn)。

這樣在編譯時(shí)如果沒(méi)有找到相應(yīng)的實(shí)現(xiàn)函數(shù),就會(huì)使用默認(rèn)的實(shí)現(xiàn),而不會(huì)導(dǎo)致編譯錯(cuò)誤。

而在 Port 中,只需要實(shí)現(xiàn)需要的函數(shù)即可,如果某些函數(shù)不需要實(shí)現(xiàn),可以不用管它,因?yàn)樵诰幾g時(shí)會(huì)使用 Common 中的默認(rèn)實(shí)現(xiàn)。

這樣,在添加新的平臺(tái)支持時(shí),只需要實(shí)現(xiàn)需要的函數(shù),而不用實(shí)現(xiàn)所有的函數(shù),大大簡(jiǎn)化了開(kāi)發(fā)的難度和工作量。同時(shí),也避免了函數(shù)指針的內(nèi)存占用和運(yùn)行時(shí)掛載函數(shù)指針的復(fù)雜性和容易出錯(cuò)問(wèn)題。

弱函數(shù)的多編譯器支持

在不同的平臺(tái)上, weak 的聲明方法也會(huì)有所不同,因此需要自己定義一個(gè) weak 聲明,例如 MY_WEAK,來(lái)支持不同的平臺(tái):

/* Compiler */
#if defined(__ARMCC_VERSION) && (__ARMCC_VERSION >= 5000000) /* ARM Compiler \\
                                                              */
#define MY_WEAK __attribute__((weak))
#elif defined(__IAR_SYSTEMS_ICC__) /* for IAR Compiler */
#define MY_WEAK __weak
#elif defined(__MINGW32__) /* MINGW32 Compiler */
#define MY_WEAK
#elif defined(__GNUC__) /* GNU GCC Compiler */
#define MY_WEAK __attribute__((weak))
#endif
/* default MY_WEAK */
#ifndef MY_WEAK
#define MY_WEAK
#endif

可以看到,在不同的編譯器下,weak 有不同的寫(xiě)法,上面的這些定義包含了對(duì) armcc5、 armclang、IAR、GCC 的支持。

然而,弱函數(shù)的方案在一些平臺(tái)有一些明顯的缺陷,例如,MSVC編譯器是微軟公司開(kāi)發(fā)的C/C++編譯器,在Windows操作系統(tǒng)下被廣泛使用。與GCC和Clang等主流編譯器相比,MSVC對(duì)于弱函數(shù)的支持不太完善。

MSVC 中,可以通過(guò)使用#pragma weak來(lái)聲明弱函數(shù),但是這個(gè)特性只能在 x86 和 x64 平臺(tái)下使用,而在 ARM 平臺(tái)下是不支持的。

此外,在一些版本的MSVC編譯器中,#pragma weak 的功能也存在一些限制和bug,所以一般在 MSVC 中直接取消 weak 還會(huì)更實(shí)際一些。

用弱函數(shù)對(duì) Port 進(jìn)行分類

weak 的引入使得我們的 Port 可以根據(jù)實(shí)際的需求進(jìn)行劃分,而不是一股腦地必須實(shí)現(xiàn)所有的 Port 函數(shù)。

引入了 weak 之后,我們就有條件將 Port 劃分為以下的幾種:

1.核心且無(wú)默認(rèn)實(shí)現(xiàn)的 Port

這屬于必須實(shí)現(xiàn)的 Port,缺乏這個(gè) Port,模塊的核心功能就運(yùn)行不起來(lái)。例如模擬 IIC中對(duì) IO 的操作 Port 函數(shù),這種 Port 用不用 weak 的區(qū)別不大,屬于最硬的骨頭,在設(shè)計(jì)軟件時(shí)應(yīng)當(dāng)注意盡可能地減少這種 Port。

在設(shè)計(jì)軟件時(shí),可以直接取消這類 Port 的弱定義,讓編譯器在編譯時(shí)就拋出錯(cuò)誤。既然缺少了這類 Port 系統(tǒng)的核心功能就無(wú)法工作,那么編譯通過(guò)了也沒(méi)有什么意義。

  1. 核心且有默認(rèn)實(shí)現(xiàn)的 Port

這類 Port 可以直接定義一個(gè)默認(rèn)實(shí)現(xiàn),在大多數(shù)情況下,用戶就可以不用管這個(gè) Port 了,而在有定制需求的場(chǎng)合下,又可以靈活地定制。

例如弱定義一個(gè) port_printf 用來(lái)支持跨平臺(tái)軟件的打印輸出,默認(rèn)是直接使用平臺(tái)的 vprintf,對(duì)于大多數(shù)的用戶來(lái)說(shuō),只需要打印到平臺(tái)自帶的 printf 即可,因此對(duì)于大多數(shù)用戶來(lái)說(shuō),這個(gè) Port 不用實(shí)現(xiàn),就能正常使用系統(tǒng)了。

attribute((weak)) void port_printf(char* fmt, ...) {
  va_list args;
  va_start(args, fmt);
  vprintf(fmt, args);
  va_end(args);
}

而有定制需求的用戶可以通過(guò)自己重寫(xiě) port_printf() 來(lái)打印到其他的地方(比如輸出到 log,或者輸出到其他串口)。

在 printf 的例子中,我們默認(rèn)了 printf 是所有的平臺(tái)都提供了的,對(duì) printf 進(jìn)行這種假設(shè)是合理的,因?yàn)樗?libc 的標(biāo)準(zhǔn)函數(shù)。

然而,有些 Port 雖然有默認(rèn)實(shí)現(xiàn),卻不能支持所有的平臺(tái),例如線程操作的 Port,在常見(jiàn)的平臺(tái)中,如 linux、RT-Thread、FreeRTOS ,我們知道如何寫(xiě)默認(rèn)實(shí)現(xiàn),但是這些實(shí)現(xiàn)又不通用,這時(shí)我們可以在默認(rèn)實(shí)現(xiàn)中結(jié)合條件編譯,為常見(jiàn)的平臺(tái)提供默認(rèn)實(shí)現(xiàn),例如:

attribute((weak)) void port_thread_start(port_thread_t* thread) {
#ifdef __linux
    pthread_mutex_lock(&(thread- >mutex));
    pthread_cond_signal(&(thread- >cond));
    pthread_mutex_unlock(&(thread- >mutex));
#elif USE_FREERTOS
    vTaskResume(thread- >thread);
#else
    #error "port_thread_start() 需要用戶實(shí)現(xiàn)"
#endif
}

這個(gè)例子中為 linux 和 FreeRTOS 提供了默認(rèn)的線程啟動(dòng) Port 的實(shí)現(xiàn),使用 linux 或者 FreeRTOS 的用戶可以通過(guò)條件編譯來(lái)直接使用默認(rèn)實(shí)現(xiàn)。

而既不用 linux 也不用 FreeRTOS 的用戶,則會(huì)在編譯時(shí)遇到 #error,這提示他們要自己實(shí)現(xiàn) port_thread_start()。

這種寫(xiě)法還有一種好處,就是在不支持 weak 的平臺(tái),例如 MSVC,就可以通過(guò) _WIN32 條件編譯來(lái)進(jìn)行跨平臺(tái)支持。

  1. 邊緣但無(wú)默認(rèn)實(shí)現(xiàn)的 Port

還有一類 Port,它們比較冷門,只有部分用戶會(huì)使用到,但是又難以提供默認(rèn)的實(shí)現(xiàn)。例如用 port_reboot() 來(lái)重啟硬件,每個(gè)硬件平臺(tái)重啟硬件的 API 都是不同的,我們無(wú)法提供一個(gè)默認(rèn)的實(shí)現(xiàn)。

但是,沒(méi)有這個(gè) Port,也不影響系統(tǒng)的核心功能,只是在某些時(shí)候(例如開(kāi)啟了超時(shí)自動(dòng)重啟功能),又有這個(gè) Port才行,這樣的 Port 就屬于是邊緣但無(wú)默認(rèn)實(shí)現(xiàn)的 Port。

這時(shí),就可以選擇將 Port 缺失的錯(cuò)誤延后到運(yùn)行時(shí),具體在操作時(shí),就可以編寫(xiě)一個(gè) weak 的實(shí)現(xiàn),而這個(gè)實(shí)現(xiàn)中拋出一個(gè)運(yùn)行時(shí)錯(cuò)誤,例如:

attribute((weak)) void port_reboot(void){
    printf("Error: port_reboot() 需要用戶實(shí)現(xiàn)\\r\\n");
    while(1);
}

這樣,只要不用到這個(gè) Port,都可以編譯通過(guò)且順利運(yùn)行,只有實(shí)際用到時(shí),才會(huì)在運(yùn)行時(shí)報(bào)錯(cuò)。

weak 在 GCC 鏈接靜態(tài)庫(kù)時(shí)的問(wèn)題

這里我想特別提示一種常見(jiàn)的 weak 失效的問(wèn)題,這種問(wèn)題目前我只發(fā)現(xiàn)在 gcc 鏈接靜態(tài)庫(kù)時(shí)包含 weak 會(huì)出現(xiàn)。

gcc 在鏈接靜態(tài)庫(kù)時(shí),默認(rèn)的行為是只要找到第一個(gè)(不管是不是弱符號(hào)),就會(huì)將其鏈接,然后停止繼續(xù)尋找,這樣一來(lái),如果你的 weak 是被第一個(gè)找到的,那么強(qiáng)定義的函數(shù)就失效了。

這個(gè)問(wèn)題有多種解決方案,我這里只提示一種,有更好的方案可以進(jìn)qq交流群:577623681 大家一起討論。

解決方案:使用 "-Wl,--whole-archive" 選項(xiàng)來(lái)解決。當(dāng)使用這個(gè)選項(xiàng)時(shí),鏈接器將整個(gè)庫(kù)文件都包含在鏈接輸出文件中,而不考慮這些庫(kù)文件是否實(shí)際上被使用了。這樣就可以保證弱符號(hào)在整個(gè)庫(kù)中得到了正確的鏈接,并且在可執(zhí)行文件或其他庫(kù)中保持有效。

需要注意的是,當(dāng)使用 "-Wl,--whole-archive" 選項(xiàng)時(shí),可能會(huì)將一些不必要的庫(kù)文件鏈接到最終的可執(zhí)行文件或庫(kù)中,這可能會(huì)增加最終文件的大小。因此,應(yīng)該僅在必要時(shí)使用這個(gè)選項(xiàng)。

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

    關(guān)注

    3

    文章

    4378

    瀏覽量

    64611
  • 驅(qū)動(dòng)代碼
    +關(guān)注

    關(guān)注

    2

    文章

    15

    瀏覽量

    7733
  • 符號(hào)
    +關(guān)注

    關(guān)注

    0

    文章

    55

    瀏覽量

    4553
收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    嵌入式C語(yǔ)言的符號(hào)引用

    總之,__attribute__ 起到了給編譯器提供上下文的作用,如果錯(cuò)誤的使用 __attribute__ 指令,因?yàn)榻o編譯器提供了錯(cuò)誤的上下文,由此引起的錯(cuò)誤通常很難被發(fā)現(xiàn)。
    發(fā)表于 12-23 10:36 ?469次閱讀

    各種繼電器圖形符號(hào)及其作用、特點(diǎn)

    各種繼電器圖形符號(hào)及其作用、特點(diǎn)
    發(fā)表于 08-20 09:41

    什么是上拉、下拉,有什么作用

    什么是上拉、下拉,有什么作用
    發(fā)表于 09-25 15:01

    示例固件不起作用

    示例固件不起作用?以上來(lái)自于谷歌翻譯以下為原文 Sample firmware not working?
    發(fā)表于 04-08 16:38

    C語(yǔ)言的強(qiáng)、別名是什么作用?

    ;__f")));官方指出 f()是 __f()的 別名。我的疑惑是:給函數(shù)起一個(gè)別名是什么作用?什么樣的情況下需要寫(xiě)這種語(yǔ)句?強(qiáng) 和 又代表什么?
    發(fā)表于 03-24 04:25

    什么叫做原理圖符號(hào),它的作用是什么?

    答:所謂的原理圖符號(hào),就是我們?cè)诶L制原理圖時(shí),需要用一些符號(hào)來(lái)代替實(shí)際的元器件,這樣的符號(hào),我們就稱之為原理圖符號(hào),也稱之為原理圖庫(kù)。它的作用
    發(fā)表于 03-22 14:35

    電源符號(hào)在數(shù)字電路中有何作用

    常見(jiàn)的電源符號(hào)有哪幾種?電源符號(hào)在數(shù)字電路中有何作用?
    發(fā)表于 11-04 07:44

    電子元器件符號(hào)-電氣符號(hào)大全-電路圖符號(hào)大全

    電子元器件符號(hào) 電氣符號(hào)大全 電路圖符號(hào)大全 導(dǎo)電體對(duì)電流的阻礙作用稱為電阻,用符號(hào)R表示,單位為歐姆、千歐、兆歐,分別用?、K?、M?表示
    發(fā)表于 10-09 14:19 ?73.9w次閱讀
    電子元器件<b class='flag-5'>符號(hào)</b>-電氣<b class='flag-5'>符號(hào)</b>大全-電路圖<b class='flag-5'>符號(hào)</b>大全

    壓敏電阻符號(hào)怎么表示 壓敏電阻選型參數(shù)及作用

    本文為您講解壓敏電阻符號(hào)電路表示方法,壓敏電阻的工作原理,壓敏電阻選型型號(hào)及參數(shù)、作用等方面內(nèi)容,希望通過(guò)全篇能給你完整了解壓敏電阻的相關(guān)知識(shí)。
    發(fā)表于 09-22 16:31 ?8.9w次閱讀
    壓敏電阻<b class='flag-5'>符號(hào)</b>怎么表示 壓敏電阻選型參數(shù)及<b class='flag-5'>作用</b>

    磁簧開(kāi)關(guān)符號(hào)_磁簧開(kāi)關(guān)的作用

    本文主要闡述了磁簧開(kāi)關(guān)的符號(hào)作用
    發(fā)表于 01-08 09:14 ?4871次閱讀
    磁簧開(kāi)關(guān)<b class='flag-5'>符號(hào)</b>_磁簧開(kāi)關(guān)的<b class='flag-5'>作用</b>

    交流接觸器的作用_交流接觸器的文字符號(hào)是什么

    本文主要介紹了交流接觸器的作用及交流接觸器的文字符號(hào)。
    的頭像 發(fā)表于 03-12 09:57 ?3.4w次閱讀

    符號(hào)在人工智能中的作用

    符號(hào)是我們用來(lái)表示其他事物的事物。符號(hào)在人類的思想和推理過(guò)程中起著至關(guān)重要的作用。如果我告訴你我看見(jiàn)貓爬在樹(shù)上,那么你的腦海就會(huì)迅速聯(lián)想到圖像。
    的頭像 發(fā)表于 07-17 10:46 ?5961次閱讀

    C語(yǔ)言強(qiáng)/符號(hào)和強(qiáng)/引用的作用

    在編程者沒(méi)有顯示指定時(shí),編譯器對(duì)強(qiáng)弱符號(hào)的定義會(huì)有一些默認(rèn)行為,同時(shí)開(kāi)發(fā)者也可以對(duì)符號(hào)進(jìn)行指定,使用"attribute((weak))"來(lái)聲明一個(gè)符號(hào)
    的頭像 發(fā)表于 07-12 11:55 ?1635次閱讀

    關(guān)于有符號(hào)數(shù)據(jù)類型的示例

    我們學(xué)習(xí)一下Systemverilog中的有符號(hào)數(shù)據(jù)類型的賦值。
    的頭像 發(fā)表于 10-17 14:40 ?1302次閱讀

    相互作用對(duì)有機(jī)光電性質(zhì)調(diào)控的理論研究

    相較于共價(jià)鍵相互作用,分子內(nèi)非共價(jià)相互作用是一種的兩個(gè)原子之間或者兩個(gè)基團(tuán)之間的非鍵相互作用。
    的頭像 發(fā)表于 07-31 17:12 ?1441次閱讀
    <b class='flag-5'>弱</b>相互<b class='flag-5'>作用</b>對(duì)有機(jī)光電性質(zhì)調(diào)控的理論研究