chinese直男口爆体育生外卖, 99久久er热在这里只有精品99, 又色又爽又黄18禁美女裸身无遮挡, gogogo高清免费观看日本电视,私密按摩师高清版在线,人妻视频毛茸茸,91论坛 兴趣闲谈,欧美 亚洲 精品 8区,国产精品久久久久精品免费

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

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

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

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

科技綠洲 ? 來源:嵌入式大雜燴 ? 作者:嵌入式大雜燴 ? 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)可以很好地解決該問題。弱符號(hào)的對(duì)象具有可以被重定義的功能(即可以被重載)。下面通過測(cè)試說明弱符號(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ù)來替代它;若沒有重新定義一個(gè)強(qiáng)函數(shù)來替換它,就使用弱函數(shù)的實(shí)現(xiàn)。弱函數(shù)就好像是一個(gè)可以被替換的“默認(rèn)函數(shù)”。

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

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

弱引用

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

弱引用是相對(duì)于強(qiáng)引用而言的。未通過 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)心編譯不通過。如果,我沒有時(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)行說明。

測(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ò)。通過使用弱引用可以實(shí)現(xiàn)后期優(yōu)化代碼的功能。而避免改動(dòng)使用該函數(shù)的地方。使用弱函數(shù)可以實(shí)現(xiàn)類似“鉤子(hook)"函數(shù)的功能。

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

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

總結(jié)

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

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

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

或許讀者有疑惑,為什么同樣是 stm32 ,卻分成三個(gè)平臺(tái)呢?這是因?yàn)閺目缙脚_(tái)軟件編寫者的角度看,只要調(diào)用的庫的 API 不一致,就和換一個(gè)不同的平臺(tái)沒有什么本質(zhì)的差別,如果在代碼中寫死了 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)了不同,所以下文中所說的“平臺(tái)”,實(shí)際上對(duì)應(yīng)的是一套 API 。

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

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

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

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

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

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

先從最樸素的方案講起。

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

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

這種樸素的方案雖然看起來簡(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è)問題是,使用不同的編譯工具鏈,添加編譯文件的方式并不一樣,例如,Visual Studio 和 MDK keil 通常是手動(dòng)添加,而 CMake 通常直接添加目錄或者通過文件后綴進(jìn)行搜索。用戶在使用不同的編譯工具時(shí),需要針對(duì)編譯工具來分別處理,這也增加了維護(hù)的成本。

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

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

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

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

方案二、條件編譯

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

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

例如,在 STM32 標(biāo)準(zhǔn)庫中,可以使用 GPIO_SetPinMode 和 GPIO_WritePin 接口來模擬 IIC 協(xié)議,而在 STM32 HAL 庫中,可以使用 HAL_GPIO_WritePin 和 HAL_GPIO_ReadPin 接口來模擬 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í),我們可以通過宏定義來選擇編譯使用哪個(gè)平臺(tái)的代碼,

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

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

方案三 函數(shù)指針

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

具體實(shí)現(xiàn)可以通過在 Common 中定義一些函數(shù)指針類型來實(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 中就可以通過調(diào)用這些函數(shù)指針來訪問 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)存的開銷,二是函數(shù)指針需要在初始化時(shí)進(jìn)行加載。

具體來說,函數(shù)指針的內(nèi)存開銷相對(duì)于代碼本身并不大,通??梢院雎圆挥?jì),但在嵌入式系統(tǒng)中,資源有限,內(nèi)存開銷需要更加注意。

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

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

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

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

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

具體實(shí)現(xiàn)可以通過在 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 中就可以通過調(diào)用這些函數(shù)來訪問 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ò)問題。以及避免了函數(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)目都無法編譯通過。

接下來,就要有請(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)目都無法編譯通過。

方案五 弱函數(shù)

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

weak 函數(shù)是一種特殊的函數(shù)類型,帶 weak 的函數(shù)和不帶 weak 的函數(shù)可以同時(shí)存在,如果有不帶 weak 的函數(shù),就會(huì)優(yōu)先鏈接不帶 weak 的實(shí)現(xiàn),如果沒有找到不帶 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 語言中,可以使用 attribute((weak)) 來聲明一個(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í)如果沒有找到相應(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)化了開發(fā)的難度和工作量。同時(shí),也避免了函數(shù)指針的內(nèi)存占用和運(yùn)行時(shí)掛載函數(shù)指針的復(fù)雜性和容易出錯(cuò)問題。

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

在不同的平臺(tái)上, weak 的聲明方法也會(huì)有所不同,因此需要自己定義一個(gè) weak 聲明,例如 MY_WEAK,來支持不同的平臺(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 有不同的寫法,上面的這些定義包含了對(duì) armcc5、 armclang、IAR、GCC 的支持。

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

MSVC 中,可以通過使用#pragma weak來聲明弱函數(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.核心且無默認(rèn)實(shí)現(xiàn)的 Port

這屬于必須實(shí)現(xiàn)的 Port,缺乏這個(gè) Port,模塊的核心功能就運(yùn)行不起來。例如模擬 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)的核心功能就無法工作,那么編譯通過了也沒有什么意義。

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

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

例如弱定義一個(gè) port_printf 用來支持跨平臺(tái)軟件的打印輸出,默認(rèn)是直接使用平臺(tái)的 vprintf,對(duì)于大多數(shù)的用戶來說,只需要打印到平臺(tái)自帶的 printf 即可,因此對(duì)于大多數(shù)用戶來說,這個(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);
}

而有定制需求的用戶可以通過自己重寫 port_printf() 來打印到其他的地方(比如輸出到 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,在常見的平臺(tái)中,如 linux、RT-Thread、FreeRTOS ,我們知道如何寫默認(rèn)實(shí)現(xiàn),但是這些實(shí)現(xiàn)又不通用,這時(shí)我們可以在默認(rèn)實(shí)現(xiàn)中結(jié)合條件編譯,為常見的平臺(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 的用戶可以通過條件編譯來直接使用默認(rèn)實(shí)現(xiàn)。

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

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

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

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

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

這時(shí),就可以選擇將 Port 缺失的錯(cuò)誤延后到運(yùn)行時(shí),具體在操作時(shí),就可以編寫一個(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,都可以編譯通過且順利運(yùn)行,只有實(shí)際用到時(shí),才會(huì)在運(yùn)行時(shí)報(bào)錯(cuò)。

weak 在 GCC 鏈接靜態(tài)庫時(shí)的問題

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

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

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

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

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

聲明:本文內(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

    文章

    4379

    瀏覽量

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

    關(guān)注

    2

    文章

    15

    瀏覽量

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

    關(guān)注

    0

    文章

    55

    瀏覽量

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

掃碼添加小助手

加入工程師交流群

    評(píng)論

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

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

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

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

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

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

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

    示例固件不起作用

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

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

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

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

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

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

    常見的電源符號(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ān)知識(shí)。
    發(fā)表于 09-22 16:31 ?8.9w次閱讀
    壓敏電阻<b class='flag-5'>符號(hào)</b>怎么表示 壓敏電阻選型參數(shù)及<b class='flag-5'>作用</b>

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

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

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

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

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

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

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

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

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

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

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

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