今天在使用 Keil (主要是 armcc 編譯器)編譯代碼的時候遇到了有 __weak 關鍵字的函數不起作用的問題,甚是奇怪。之前對於 __weak
關鍵字一直是一個簡單的認知:編譯器自動使用沒有 __weak
的同名函數(如果有的話)替換有 __weak
關鍵字的同名函數,__weak 函數可以沒有定義,且編譯器不會報錯! 至於這個參數詳細的使用細節一直是一知半解,今天藉此機會,以 GCC 作爲對比,來學習一下 ARM 中的 __weak 關鍵字的具體使用!
來源
使用過 GCC 以及有 linux 編程經驗的人,對於這個關鍵字應該不陌生。GNU 的編譯器(gcc)擴展了一個關鍵字 __attribute__
,通過該關鍵字,用戶可以在聲明時指定特殊的屬性,使用時該關鍵字後跟雙括號內的屬性,例如:__attribute__((屬性名字))
。屬性名字都是定義好的,Weak 屬性就是其中之一:__attribute__((weak))
。在 linux 源碼中,該關鍵字非常常見:
GCC 不多介紹,重點關注 ARM。在 ARM 編譯器(armcc)中,支持和 GCC 相同的關鍵字 __attribute__
,使用方式也基本相同,如下:
__attribute__((attribute1, attribute2, ...)) // 例如:void * Function_Attributes_malloc_0(int b) __attribute__((malloc));
__attribute__((__attribute1__, __attribute2__, ...)) // 例如:static int b __attribute__((__unused__));
當函數屬性發生衝突時,編譯器將使用更安全或更強的一個
除此之外,ARM 編譯器(armcc)還擴展了一個關鍵字 __weak
,例如:__weak void f(void); 或者 __weak int i;
。ARM 的彙編器(armasm)以另一種方式 [WEAK]
支持該特性。
被 __attribute__((weak))
或者 __weak
修飾的符號,我們稱之爲 弱符號(Weak Symbol)。例如:弱函數、弱變量。
作用範圍
__attribute__
關鍵字使您可以指定變量或結構字段,函數和類型的特殊屬性。__weak
關鍵字可以應用於函數和變量聲明以及函數定義。下面我們詳細介紹一下這兩個關鍵字的具體用法。
強符號
在 GCC 中,沒有 __attribute__((weak))
修飾的符號被稱爲強符號。對於強符號,鏈接器的處理我們比較常見。在 ARM 中,如果鏈接器無法在到目前爲止已加載內容中解析對正常非弱符號的引用問題,則它會嘗試通過在庫中找到符號來解決此問題:
- 如果找不到此類引用,則鏈接器將報告錯誤。
- 如果解析了這樣的引用,則從入口點可以通過至少一個非弱引用訪問的部分被標記爲已使用。 這樣可以確保鏈接器不會將該節作爲未使用的節刪除。 每個非弱引用都必須通過一個定義來解決。 如果有多個定義,則鏈接器將報告錯誤。
鏈接器不會從庫中加載對象來解析弱引用。
__weak
聲明
__weak
可以用於函數聲明或者變量的聲明。對於聲明,此存儲類指定一個 extern 對象聲明,即使該對象不存在,該聲明也不會導致鏈接器對未解析的引用進行錯誤處理。如下圖:
如果 __weak
聲明可以找到定義,則會用找到的定義替換 __weak
引用;對於沒有定義的聲明(函數或變量),編譯器做如下處理:
- 引用被解析爲分支連接指令
BL
。等效於將被引用的分支爲NOP
- 直接將引用替換爲
NOP
指令
如下圖:
定義
用 __weak
定義的函數弱輸出其符號。弱定義的函數的行爲類似於正常定義的函數,除非將同名的非弱定義的函數鏈接到同一鏡像中。 如果在同一鏡像中同時存在非弱定義函數和弱定義函數,則對該函數的所有調用都會解析爲調用非弱函數,否則直接使用弱定義的函數(與上面的若聲明不同)。
如果可以使用多個弱定義,則除非使用鏈接器選項 --muldefweak
,否則鏈接器會生成一條錯誤消息。在這種情況下,鏈接器隨機選擇一個供所有調用來使用。使用方式如下:
/* a.h */
void FuncA(void);
void FuncB(void);
/* a.c */
void FuncA(void)
{
FuncB(); /* 這裏將替換爲 main.c 中的 FuncB */
}
__weak void FuncB(void) /* 弱定義 */
{
}
/* main.c */
void FuncB(void)
{
}
用
__weak
聲明然後不使用__weak
定義的函數的行爲相當於非弱函數。
限制
- 函數或變量不能在同一編譯中同時弱和非弱地使用。
void f(void);
void g()
{
f(); /* 非弱函數引用 */
}
__weak void f(void);
void h()
{
f(); /* 弱函數引用 */
}
- 不能在定義函數或變量的同一編譯中使用弱函數或弱變量,如下將導致編譯錯誤(正確的使用方式參考上面的使用示例):
/* a.c 如下同一文件中的定義及使用將報錯 */
__weak void f(void);
void h()
{
f();
}
void f()
{
}
- 弱函數不能是內聯函數
__attribute__((weak))
該關鍵字的作用與 __weak
的作用是一樣的,只是在使用時有些不同。
聲明
這個參數是 GUN 編譯器的一個擴展,ARM 編譯器也支持該關鍵字。_attribute__((weak))
可以聲明弱變量,並且其聲明方式與 __weak
稍有區別。例如:extern int Variable_Attributes_weak_1 __attribute__((weak)); 或者
_attribute__((weak))
可以聲明弱函數,其與 __weak
稍有不同。例如:extern int Function_Attributes_weak_0 (int b) __attribute__((weak));
。
用 __attribute__((weak))
聲明然後不使用 __attribute__((weak))
進行定義的函數的行爲就像是弱函數。 這與 __weak
關鍵字不同。
在 GNU 模式中需要 extern 限定符。在非 gnu 模式下,編譯器假設如果變量不是 extern,那麼它將像對待其他非弱變量一樣對待。
定義
用 __attribute__((weak))
定義的函數弱輸出其符號(與 __weak
相同)。其使用方式有以下兩種:
__attribute__((weak)) void FuncA(void)
{
printf("Weak FuncA!\r\n");
}
/* 或者 */
void __attribute__((weak)) FuncA(void)
{
printf("Weak FuncA!\r\n");
}
區別
- 如上介紹,
__weak
和__attribute__((weak))
在聲明和定義的時候,其所處的位置有不同。 __weak
僅在函數定義中使用時纔會生成弱函數。而在任何情況下__attribute__((weak))
都會生成弱函數,無論是用於函數定義還是用於函數聲明中。
參考
- https://community.arm.com/developer/tools-software/tools/f/keil-forum/34584/run-error-when-use-__weak-to-define-function
- https://blog.csdn.net/rensheng__rumeng/article/details/78634804
- http://blog.sina.com.cn/s/blog_62d3426b0100g7n6.html
- http://www.keil.com/support/man/docs/armcc/armcc_chr1359124970859.htm
- http://infocenter.arm.com/help/topic/com.arm.doc.dui0472j/DUI0472J_armcc_user_guide.pdf
- https://github.com/ARM-software/CMSIS_5/issues/141