弱符號
弱符號是指在定義或者聲明一個對象(變量、結(jié)構(gòu)體成員、函數(shù))時,在對象的前面添加 attribute ((weak)) 標(biāo)志所得到的對象符號。如下所示函數(shù)即為一個弱對象符號 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");
}
弱符號的作用與示例
弱符號是相對于強符號而言的,在定義或者聲明變量、函數(shù)時,未添加 attribute ((weak)) 標(biāo)識的就默認(rèn)為強符號。如下,最普通的函數(shù)定義,就是定義了一個強符號 void test_strong_ref(void):
void test_weak_attr(void)
{
printf("this is a strong func\\r\\n");
}
驅(qū)動程序往往需要考慮兼容性,因為要兼任很多廠商的不同型號的設(shè)備。若驅(qū)動程序中使用強符號定義一些與適配的設(shè)備的特性相關(guān)的功能,則下次適配其他設(shè)備時,該強符號函數(shù)可能需要被修改,以兼容新的設(shè)備。當(dāng)適配的設(shè)備很多時,頻繁地更改驅(qū)動代碼將破壞驅(qū)動的可維護性。
弱符號的出現(xiàn)可以很好地解決該問題。弱符號的對象具有可以被重定義的功能(即可以被重載)。下面通過測試說明弱符號這種可被重載的特性。
在 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();
}
編譯運行該 main.c 程序,得到的結(jié)果是什么樣子的呢?
將 main.c 中的 void test_weak_attr(void) 函數(shù)注釋掉,再重新編譯運行程序得到的結(jié)果是:
小結(jié):在使用弱符號函數(shù)時,我們可以重新定義一個同名的強符號函數(shù)來替代它;若沒有重新定義一個強函數(shù)來替換它,就使用弱函數(shù)的實現(xiàn)。弱函數(shù)就好像是一個可以被替換的“默認(rèn)函數(shù)”。
值得一提的是,舊版本的編譯器還可以使用如下方式的定義(僅聲明無效)將一個對象定義為一個弱對象:
在 linux 的一些代碼中,__weak 其實就是通過 attribute ((weak))的重命名,兩者等效。
弱引用
弱引用是在聲明一個對象時,通過__attribute__ ((weakref()) 定義一個符號的引用關(guān)系。如下所示即定義 test_weakref() 函數(shù)弱引用 test_weak_ref() 函數(shù)。
弱引用是相對于強引用而言的。未通過 attribute ((weakref()) 的符號和實現(xiàn)代碼之間的關(guān)系是強引用。如下即為一個強引用函數(shù)。它直接給出了 函數(shù) test_strong_ref(void) 的實現(xiàn)。
在編譯程序的時候,我們可以直接使用 test_strong_ref(void) 而不必?fù)?dān)心編譯不通過。如果,我沒有時間去實現(xiàn) test_strong_ref(void) ,還想在程序里先使用該函數(shù)那該如何呢?(是的,就是想白嫖,不想實現(xiàn),還想先在程序里使用這個函數(shù))。
這個時候弱引用就派上用場了??梢韵葘⒃摵瘮?shù)定義為弱引用插入到代碼中,待后期有時間再慢慢優(yōu)化代碼實現(xiàn)這個函數(shù)完整的功能。下面結(jié)合測試進行說明。
測試代碼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");
}
}
測試結(jié)果:
There is no weakref
測試代碼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");
}
}
測試結(jié)果:
this is a weak ref
小結(jié):強引用,在未定義該強引用的實現(xiàn)時,編譯會報錯誤:未定義的引用。弱引用允許定義一個未實現(xiàn)(未實例化)的對象,這在編譯的時候會將該對象處理成 NULL,編譯器并不會報錯。通過使用弱引用可以實現(xiàn)后期優(yōu)化代碼的功能。而避免改動使用該函數(shù)的地方。使用弱函數(shù)可以實現(xiàn)類似“鉤子(hook)"函數(shù)的功能。
實際上,包括C、python、go 編程語言在內(nèi)的很多語言 都有類似用法,本篇文章敘述的方法同樣適用于這些語言的相關(guān)開發(fā)。
注意:弱引用僅在靜態(tài)編譯中有效,動態(tài)鏈接中可能無效。
總結(jié)
弱符號、弱引用都是增強程序的可維護性的方法。弱符號通過可以被重定義的特性,實現(xiàn)可以被替換實現(xiàn)。弱引用通過可以暫時使用一個未定義的函數(shù)的功能,實現(xiàn)允許后期再實現(xiàn)該函數(shù)具體功能,而不必?fù)?dān)心編譯不通過。
為了方便理解,我們先預(yù)設(shè)一個應(yīng)用場景:
我們編寫了一個模擬IIC的驅(qū)動,希望它能夠在不同的的平臺運行,目標(biāo)的平臺就設(shè)為 stm32 標(biāo)準(zhǔn)庫,stm32 HAL 庫,stm32 LL 庫,和 RT-Thread Driver 驅(qū)動庫。
或許讀者有疑惑,為什么同樣是 stm32 ,卻分成三個平臺呢?這是因為從跨平臺軟件編寫者的角度看,只要調(diào)用的庫的 API 不一致,就和換一個不同的平臺沒有什么本質(zhì)的差別,如果在代碼中寫死了 API 的調(diào)用,即使是同一個平臺,仍然像多平臺一樣不能運行。
由此可以看出,跨平臺的困難所在,也不是由硬件平臺所導(dǎo)致的,而是由代碼所依賴的 API 的不同導(dǎo)致的。同一個平臺,如果依賴的 API 不同,代碼就不能跨平臺,同樣地,不同的平臺,如果依賴的 API 相同,也可以跨平臺。
所以歸根結(jié)底,是代碼所依賴的 API 出現(xiàn)了不同,所以下文中所說的“平臺”,實際上對應(yīng)的是一套 API 。
我們繼續(xù)說這個模擬 IIC 的驅(qū)動,模擬 IIC 驅(qū)動是使用 GPIO 的反轉(zhuǎn)來模擬 IIC 協(xié)議,所以依賴了平臺的 GPIO API,如果把調(diào)用 GPIO 的部分寫死,那么換一個平臺,就肯定不能在多個平臺上運行。
下面我們開始討論在多平臺運行的解決方案。
我們先從最樸素簡單的解決方案開始,然后逐步迭代到弱函數(shù)的方案,這樣有兩方面好處:
一是從簡單的方案開始,循序漸進地介紹,可以降低閱讀門檻。
二是可以帶讀者親歷一遍方案的演進過程,以及演進動因。
和給直接給結(jié)果相比,注重過程和動因更能夠還原技術(shù)決策的真實過程。因為任何技術(shù)都是從簡單樸素逐步演進而來的,如果直接給出最后的結(jié)果,會產(chǎn)生理解的斷層,即使記住了幾種技術(shù)的優(yōu)劣,在新的場景中,面對更加多樣化的實際問題也會難免乏力。
先從最樸素的方案講起。
方案一、手動控制添加編譯的 .c 文件
樸素的方案有很多,比如就是多搞幾個版本的 .c 文件,比如SIMU_IIC_STM32_HAL.c ,SIMU_IIC_STM32_LL.c, SIMU_IIC_RTT.c 需要哪個就添加哪個進去編譯不就完了嘛!
這種樸素的方案雖然看起來簡單,但是,這幾個文件中包含有共用的邏輯,例如模擬 IIC 的協(xié)議的實現(xiàn),如何將 8bit 的數(shù)據(jù)依次發(fā)送,等等。
這些共用的邏輯,相當(dāng)于在每個文件中都復(fù)制了一份,一旦修改到共用的邏輯,就要手動同步每個文件,這會導(dǎo)致代碼冗余和維護難度的急劇增加,很容易出現(xiàn)人為失誤。如果需要添加更多的平臺支持,就需要再次復(fù)制修改代碼。
另一個問題是,使用不同的編譯工具鏈,添加編譯文件的方式并不一樣,例如,Visual Studio 和 MDK keil 通常是手動添加,而 CMake 通常直接添加目錄或者通過文件后綴進行搜索。用戶在使用不同的編譯工具時,需要針對編譯工具來分別處理,這也增加了維護的成本。
因此,我們需要尋找更加優(yōu)雅的解決方案。
方案一中最核心的問題,是沒有分離共用的邏輯,和各個平臺的適配接口。
在通常的命名慣例中,共用的邏輯稱為 Common,而各個平臺的適配接口稱為 Port。
在接下來的方案中,我們就會引入 Common 和 Port 分離的設(shè)計思想,Common 和 Port 的分離,使得對共用邏輯的修改和增強直接 “分發(fā)” 到了各個的 Port 中,而增添新的平臺,不需要對 Common 做任何修改。
方案二、條件編譯
一種更加優(yōu)雅的解決方案是使用條件編譯。條件編譯是一種編譯時根據(jù)條件選擇編譯代碼的技術(shù),可以通過編譯器提供的宏定義和預(yù)處理指令來實現(xiàn)。
在我們的模擬 IIC 驅(qū)動中,可以直接編寫 Common 部分,然后 Common 部分通過條件編譯,可以根據(jù)平臺的不同選擇不同的 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
這樣,在編譯代碼時,我們可以通過宏定義來選擇編譯使用哪個平臺的代碼,
從而實現(xiàn)跨平臺運行。
然而,這種條件編譯方式還是有一些問題,如果我們需要添加新的平臺支持,就需要添加新的宏定義和條件編譯,而且需要修改模塊的源碼。
方案三 函數(shù)指針
我們可以進一步分離 Common 和 Port,將其放在不同的 .c 文件中,Common 通過函數(shù)調(diào)用的方式來訪問 Port 提供的接口,這樣可以更加靈活和方便地添加新的平臺支持。
具體實現(xiàn)可以通過在 Common 中定義一些函數(shù)指針類型來實現(xiàn)。
例如,我們可以定義一個名為 IICOps 的結(jié)構(gòu)體,其中包含了一些指向函數(shù)的指針,這些函數(shù)實現(xiàn)了具體的 IIC 操作。
在 Port 中,我們實現(xiàn)這些函數(shù),并將其指針傳遞給 Common 中的 IICOps 結(jié)構(gòu)體。
這樣,在 Common 中就可以通過調(diào)用這些函數(shù)指針來訪問 Port 提供的接口了。
這種方案的好處是,添加新的平臺支持時,只需要實現(xiàn)相應(yīng)的 Port 函數(shù),并將其指針傳遞給 Common 中的結(jié)構(gòu)體即可,不需要修改 Common 的源碼。
同時,由于 Common 和 Port 分離,不同平臺的適配代碼可以相互獨立,修改一方不會影響到另一方,從而減少了代碼冗余和維護難度。
但是,使用函數(shù)指針有兩個主要的缺點,一是函數(shù)指針本身需要占據(jù)內(nèi)存,增加了內(nèi)存的開銷,二是函數(shù)指針需要在初始化時進行加載。
具體來說,函數(shù)指針的內(nèi)存開銷相對于代碼本身并不大,通??梢院雎圆挥?,但在嵌入式系統(tǒng)中,資源有限,內(nèi)存開銷需要更加注意。
而函數(shù)指針的初始化需要在程序啟動時進行,這也會對程序的啟動時間產(chǎn)生一定的影響。此外,函數(shù)指針的使用可能會導(dǎo)致一定的運行時開銷,需要在程序性能和資源利用方面做出權(quán)衡。
另外,函數(shù)指針的使用需要程序員有一定的技術(shù)水平和經(jīng)驗,需要合理地進行函數(shù)指針的定義、傳遞和調(diào)用等操作,避免出現(xiàn)潛在的錯誤和安全問題。
總的來說,函數(shù)指針是一種比較靈活和方便的方式,可以幫助我們實現(xiàn)代碼的跨平臺支持,但需要在實際應(yīng)用中仔細(xì)考慮其使用場景和影響,做出合理的決策。
方案四、Common 中聲明,Prot 中實現(xiàn)
在這種方案中,我們?nèi)匀粚?Common 和 Port 分離,但是我們不再使用函數(shù)指針來訪問 Port 中的接口,而是將其定義為 extern 聲明,由 Port 來實現(xiàn)具體的函數(shù)。
具體實現(xiàn)可以通過在 Common 中定義一些 extern 聲明的函數(shù),這些函數(shù)實現(xiàn)了具體的 IIC 操作,但是并不在 Common 中實現(xiàn)具體的代碼邏輯,而是在 Port 中實現(xiàn)。
在 Port 中,我們實現(xiàn)這些函數(shù),并將其聲明為 extern,然后在編譯時鏈接到 Common 中。
這樣,在 Common 中就可以通過調(diào)用這些函數(shù)來訪問 Port 提供的接口了。
這種方案的好處是,添加新的平臺支持時,只需要實現(xiàn)相應(yīng)的 Port 函數(shù),并在編譯時鏈接到 Common 中即可,不需要修改 Common 的源碼。
這樣 Port 的實現(xiàn)函數(shù)的掛載就提前到了編譯階段,避免了運行時掛載函數(shù)指針的復(fù)雜性和容易出錯問題。以及避免了函數(shù)指針的內(nèi)存占用。
但是,和 IIC 的例子不同的是,在一些更實際的項目中,隨著軟件復(fù)雜度的提升, Common 中使用的 Port 函數(shù)數(shù)量會快速膨脹,這時,每個 Port 函數(shù)都必須要實現(xiàn),即使這個功能非常的冷門,這樣 Common 中每增加一個 Port 的依賴,都要求所有的 Port 進行及時的跟進,否則整個項目都無法編譯通過。
接下來,就要有請 weak (弱函數(shù))機制出馬了
但方案四的缺點在于,在一些更實際的項目中,隨著軟件復(fù)雜度的提升,Common 中使用的 Port 函數(shù)數(shù)量會快速膨脹,這時,每個 Port 函數(shù)都必須要實現(xiàn),即使這個功能非常的冷門,這樣 Common 中每增加一個 Port 的依賴,都要求所有的 Port 進行及時的跟進,否則整個項目都無法編譯通過。
方案五 弱函數(shù)
為了解決這個問題,可以使用 weak (弱函數(shù))機制,將所有的 Port 函數(shù)都定義為 weak 函數(shù)。
weak 函數(shù)是一種特殊的函數(shù)類型,帶 weak 的函數(shù)和不帶 weak 的函數(shù)可以同時存在,如果有不帶 weak 的函數(shù),就會優(yōu)先鏈接不帶 weak 的實現(xiàn),如果沒有找到不帶 weak 的實現(xiàn)函數(shù),就會使用 weak 函數(shù)作為默認(rèn)的實現(xiàn)。
即,在使用弱函數(shù)時,如果找到多個實現(xiàn),鏈接器會選擇優(yōu)先級最高的實現(xiàn)。
在 C 語言中,可以使用 attribute((weak)) 來聲明一個函數(shù)為弱函數(shù)。例如:
attribute((weak)) void port_func()
{
// 默認(rèn)實現(xiàn)
}
attribute((weak)) void port_func()
而在 Port 中,只需要實現(xiàn)需要的函數(shù)即可,如果某些函數(shù)不需要實現(xiàn),可以不用管它,因為在鏈接時會使用 Common 中的默認(rèn)實現(xiàn)。
這樣在編譯時如果沒有找到相應(yīng)的實現(xiàn)函數(shù),就會使用默認(rèn)的實現(xiàn),而不會導(dǎo)致編譯錯誤。
而在 Port 中,只需要實現(xiàn)需要的函數(shù)即可,如果某些函數(shù)不需要實現(xiàn),可以不用管它,因為在編譯時會使用 Common 中的默認(rèn)實現(xiàn)。
這樣,在添加新的平臺支持時,只需要實現(xiàn)需要的函數(shù),而不用實現(xiàn)所有的函數(shù),大大簡化了開發(fā)的難度和工作量。同時,也避免了函數(shù)指針的內(nèi)存占用和運行時掛載函數(shù)指針的復(fù)雜性和容易出錯問題。
弱函數(shù)的多編譯器支持
在不同的平臺上, weak 的聲明方法也會有所不同,因此需要自己定義一個 weak 聲明,例如 MY_WEAK,來支持不同的平臺:
/* 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 有不同的寫法,上面的這些定義包含了對 armcc5、 armclang、IAR、GCC 的支持。
然而,弱函數(shù)的方案在一些平臺有一些明顯的缺陷,例如,MSVC編譯器是微軟公司開發(fā)的C/C++編譯器,在Windows操作系統(tǒng)下被廣泛使用。與GCC和Clang等主流編譯器相比,MSVC對于弱函數(shù)的支持不太完善。
MSVC 中,可以通過使用#pragma weak來聲明弱函數(shù),但是這個特性只能在 x86 和 x64 平臺下使用,而在 ARM 平臺下是不支持的。
此外,在一些版本的MSVC編譯器中,#pragma weak 的功能也存在一些限制和bug,所以一般在 MSVC 中直接取消 weak 還會更實際一些。
用弱函數(shù)對 Port 進行分類
weak 的引入使得我們的 Port 可以根據(jù)實際的需求進行劃分,而不是一股腦地必須實現(xiàn)所有的 Port 函數(shù)。
引入了 weak 之后,我們就有條件將 Port 劃分為以下的幾種:
1.核心且無默認(rèn)實現(xiàn)的 Port
這屬于必須實現(xiàn)的 Port,缺乏這個 Port,模塊的核心功能就運行不起來。例如模擬 IIC中對 IO 的操作 Port 函數(shù),這種 Port 用不用 weak 的區(qū)別不大,屬于最硬的骨頭,在設(shè)計軟件時應(yīng)當(dāng)注意盡可能地減少這種 Port。
在設(shè)計軟件時,可以直接取消這類 Port 的弱定義,讓編譯器在編譯時就拋出錯誤。既然缺少了這類 Port 系統(tǒng)的核心功能就無法工作,那么編譯通過了也沒有什么意義。
- 核心且有默認(rèn)實現(xiàn)的 Port
這類 Port 可以直接定義一個默認(rèn)實現(xiàn),在大多數(shù)情況下,用戶就可以不用管這個 Port 了,而在有定制需求的場合下,又可以靈活地定制。
例如弱定義一個 port_printf 用來支持跨平臺軟件的打印輸出,默認(rèn)是直接使用平臺的 vprintf,對于大多數(shù)的用戶來說,只需要打印到平臺自帶的 printf 即可,因此對于大多數(shù)用戶來說,這個 Port 不用實現(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 是所有的平臺都提供了的,對 printf 進行這種假設(shè)是合理的,因為它是 libc 的標(biāo)準(zhǔn)函數(shù)。
然而,有些 Port 雖然有默認(rèn)實現(xiàn),卻不能支持所有的平臺,例如線程操作的 Port,在常見的平臺中,如 linux、RT-Thread、FreeRTOS ,我們知道如何寫默認(rèn)實現(xiàn),但是這些實現(xiàn)又不通用,這時我們可以在默認(rèn)實現(xiàn)中結(jié)合條件編譯,為常見的平臺提供默認(rèn)實現(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() 需要用戶實現(xiàn)"
#endif
}
這個例子中為 linux 和 FreeRTOS 提供了默認(rèn)的線程啟動 Port 的實現(xiàn),使用 linux 或者 FreeRTOS 的用戶可以通過條件編譯來直接使用默認(rèn)實現(xiàn)。
而既不用 linux 也不用 FreeRTOS 的用戶,則會在編譯時遇到 #error,這提示他們要自己實現(xiàn) port_thread_start()。
這種寫法還有一種好處,就是在不支持 weak 的平臺,例如 MSVC,就可以通過 _WIN32 條件編譯來進行跨平臺支持。
- 邊緣但無默認(rèn)實現(xiàn)的 Port
還有一類 Port,它們比較冷門,只有部分用戶會使用到,但是又難以提供默認(rèn)的實現(xiàn)。例如用 port_reboot() 來重啟硬件,每個硬件平臺重啟硬件的 API 都是不同的,我們無法提供一個默認(rèn)的實現(xiàn)。
但是,沒有這個 Port,也不影響系統(tǒng)的核心功能,只是在某些時候(例如開啟了超時自動重啟功能),又有這個 Port才行,這樣的 Port 就屬于是邊緣但無默認(rèn)實現(xiàn)的 Port。
這時,就可以選擇將 Port 缺失的錯誤延后到運行時,具體在操作時,就可以編寫一個 weak 的實現(xiàn),而這個實現(xiàn)中拋出一個運行時錯誤,例如:
attribute((weak)) void port_reboot(void){
printf("Error: port_reboot() 需要用戶實現(xiàn)\\r\\n");
while(1);
}
這樣,只要不用到這個 Port,都可以編譯通過且順利運行,只有實際用到時,才會在運行時報錯。
weak 在 GCC 鏈接靜態(tài)庫時的問題
這里我想特別提示一種常見的 weak 失效的問題,這種問題目前我只發(fā)現(xiàn)在 gcc 鏈接靜態(tài)庫時包含 weak 會出現(xiàn)。
gcc 在鏈接靜態(tài)庫時,默認(rèn)的行為是只要找到第一個(不管是不是弱符號),就會將其鏈接,然后停止繼續(xù)尋找,這樣一來,如果你的 weak 是被第一個找到的,那么強定義的函數(shù)就失效了。
這個問題有多種解決方案,我這里只提示一種,有更好的方案可以進qq交流群:577623681 大家一起討論。
解決方案:使用 "-Wl,--whole-archive" 選項來解決。當(dāng)使用這個選項時,鏈接器將整個庫文件都包含在鏈接輸出文件中,而不考慮這些庫文件是否實際上被使用了。這樣就可以保證弱符號在整個庫中得到了正確的鏈接,并且在可執(zhí)行文件或其他庫中保持有效。
需要注意的是,當(dāng)使用 "-Wl,--whole-archive" 選項時,可能會將一些不必要的庫文件鏈接到最終的可執(zhí)行文件或庫中,這可能會增加最終文件的大小。因此,應(yīng)該僅在必要時使用這個選項。
-
函數(shù)
+關(guān)注
關(guān)注
3文章
4331瀏覽量
62618 -
驅(qū)動代碼
+關(guān)注
關(guān)注
2文章
14瀏覽量
7593 -
符號
+關(guān)注
關(guān)注
0文章
55瀏覽量
4336
發(fā)布評論請先 登錄
相關(guān)推薦
評論